diff --git a/build/fr/iut_fbleau/HexGame/Arena.class b/build/fr/iut_fbleau/HexGame/Arena.class new file mode 100644 index 0000000..3f8ddca Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/Arena.class differ diff --git a/build/fr/iut_fbleau/HexGame/ArenaMain.class b/build/fr/iut_fbleau/HexGame/ArenaMain.class new file mode 100644 index 0000000..18210ec Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/ArenaMain.class differ diff --git a/javaAPI/fr/iut_fbleau.tar b/javaAPI/fr/iut_fbleau.tar index 9094711..87874e0 100644 Binary files a/javaAPI/fr/iut_fbleau.tar and b/javaAPI/fr/iut_fbleau.tar differ diff --git a/javaAPI/fr/iut_fbleau/HexGame/Arena.java b/javaAPI/fr/iut_fbleau/HexGame/Arena.java new file mode 100644 index 0000000..7ec4064 --- /dev/null +++ b/javaAPI/fr/iut_fbleau/HexGame/Arena.java @@ -0,0 +1,62 @@ +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 bots = new ArrayList<>(); + private FileWriter csvWriter; + + public Arena() { + try { + csvWriter = new FileWriter("arena_results.csv"); + csvWriter.append("Bot 1, Bot 2, Winner\n"); + } 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(11); // Standard 11x11 Hex board + EnumMap players = new EnumMap<>(Player.class); + players.put(Player.PLAYER1, bot1); + players.put(Player.PLAYER2, bot2); + + Simulation simulation = new Simulation(board, players); // Ensure Simulation is correctly imported + return simulation.run(); + } +} diff --git a/javaAPI/fr/iut_fbleau/HexGame/ArenaMain.java b/javaAPI/fr/iut_fbleau/HexGame/ArenaMain.java new file mode 100644 index 0000000..5dac4fe --- /dev/null +++ b/javaAPI/fr/iut_fbleau/HexGame/ArenaMain.java @@ -0,0 +1,15 @@ +package fr.iut_fbleau.HexGame; + +import fr.iut_fbleau.GameAPI.Player; + +public class ArenaMain { + public static void main(String[] args) { + Arena arena = new Arena(); + arena.addBot(new RandomBot(Player.PLAYER1, 12345L)); // 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(); + } +} diff --git a/javaAPI/fr/iut_fbleau/HexGame/HeuristicBot.java b/javaAPI/fr/iut_fbleau/HexGame/HeuristicBot.java new file mode 100644 index 0000000..1666346 --- /dev/null +++ b/javaAPI/fr/iut_fbleau/HexGame/HeuristicBot.java @@ -0,0 +1,48 @@ +package fr.iut_fbleau.HexGame; + +import fr.iut_fbleau.GameAPI.*; + +public class HeuristicBot extends AbstractGamePlayer { + + public HeuristicBot(Player me) { + super(me); // Correct constructor usage + } + + @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; + + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + if (board.getPlayerAt(i, j) == Player.PLAYER1) { + score += Math.abs(i - center) + Math.abs(j - center); // Distance from center + } + } + } + return score; + } +} diff --git a/javaAPI/fr/iut_fbleau/HexGame/HexSimMain.java b/javaAPI/fr/iut_fbleau/HexGame/HexSimMain.java index c90002d..d314aa7 100644 --- a/javaAPI/fr/iut_fbleau/HexGame/HexSimMain.java +++ b/javaAPI/fr/iut_fbleau/HexGame/HexSimMain.java @@ -15,6 +15,19 @@ import java.util.Random; * 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).

+ * + * Intérêt : + * + * Reproductibilité : relancer exactement la même simulation pour déboguer / analyser. + * Comparaison équitable : comparer 2 bots sur les mêmes tirages aléatoires. + * Si aucun seed n'est fourni, on utilise généralement l'heure courante, ce qui rend chaque exécution différente.

+ * + * long seed; + * */ public class HexSimMain { @@ -170,7 +183,7 @@ public class HexSimMain { // ex: "7 10000" if (isInt(s)) { int v = Integer.parseInt(s); - if (a.size == 7) a.size = v; + if (a.size == 11) a.size = v; else a.games = v; } else { System.err.println("Unknown arg: " + s); diff --git a/javaAPI/fr/iut_fbleau/HexGame/MiniMaxBot.java b/javaAPI/fr/iut_fbleau/HexGame/MiniMaxBot.java new file mode 100644 index 0000000..bb71baf --- /dev/null +++ b/javaAPI/fr/iut_fbleau/HexGame/MiniMaxBot.java @@ -0,0 +1,89 @@ +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 + } + + @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.getPlayerAt(i, j) == Player.PLAYER1) { + score += Math.abs(i - center) + Math.abs(j - center); // Distance from center + } + } + } + return score; + } +} diff --git a/javaAPI/fr/iut_fbleau/HexGame/MonteCarloBot.java b/javaAPI/fr/iut_fbleau/HexGame/MonteCarloBot.java new file mode 100644 index 0000000..55a4e43 --- /dev/null +++ b/javaAPI/fr/iut_fbleau/HexGame/MonteCarloBot.java @@ -0,0 +1,59 @@ +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 + } + + @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 = 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 = board.safeCopy(); // Reset the board for the next simulation + } + + return (float) wins / simulations; + } +} diff --git a/javaAPI/fr/iut_fbleau/HexGame/RandomBot.java b/javaAPI/fr/iut_fbleau/HexGame/RandomBot.java index 0bc5843..b670ada 100644 --- a/javaAPI/fr/iut_fbleau/HexGame/RandomBot.java +++ b/javaAPI/fr/iut_fbleau/HexGame/RandomBot.java @@ -10,9 +10,6 @@ import java.util.Iterator; import java.util.List; import java.util.Random; -/** - * Bot non intelligent : joue un coup légal au hasard. - */ public class RandomBot extends AbstractGamePlayer { private final Random rng; @@ -28,7 +25,6 @@ public class RandomBot extends AbstractGamePlayer { @Override public AbstractPly giveYourMove(IBoard board) { - // On récupère tous les coups légaux via l'itérateur fourni par le plateau. List legal = new ArrayList<>(); Iterator it = board.iterator(); while (it.hasNext()) { diff --git a/javaAPI/fr/iut_fbleau/HexGame/Simulation.java b/javaAPI/fr/iut_fbleau/HexGame/Simulation.java new file mode 100644 index 0000000..c18f87b --- /dev/null +++ b/javaAPI/fr/iut_fbleau/HexGame/Simulation.java @@ -0,0 +1,273 @@ +package fr.iut_fbleau.HexGame; + +import fr.iut_fbleau.GameAPI.*; +import java.util.EnumMap; +import java.util.LinkedList; + + +public class Simulation extends AbstractGame { + + //ATTRIBUTS + private HexPly bestmove; + private float bestoutcome; + private int MAXDEPTH = 9; + private int EVALDEPTH = 10; + private LinkedList taken = new LinkedList(); + + //ATTRIBUTS QUE JE NE VOUDRAIS PAS CRÉER IDÉALEMENT + private IBoard simCurrentBoard; + private EnumMap simmapPlayers; + + //CONSTRUCTEUR + public Simulation(IBoard b, EnumMap m){ + super(b, m); + simCurrentBoard = b; + simmapPlayers = m; + } + + //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){ + RandomBot simplay = new RandomBot(); + HexBoard simpos = position.safeCopy(); + LinkedList ctaken = taken; + HexPly testmove; + float wins = 0; + float losses = 0; + + for(int i=0; i=losses){ + return losses/wins; + } else { + return -(wins/losses); + } + + } + + private float explMAX(HexBoard position, int depth){ + 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 = -1.0f; + HexPly bestcasemove; + HexPly testmove; + for (int i=0; i= bestcase) { + //System.out.println(" MAX new best case"); + bestcase = val; + bestcasemove = testmove; + if (depth==0) { + this.bestoutcome = bestcase; + this.bestmove = bestcasemove; + } + } + position.undoPly(); + taken.remove(t); + } + } + } + return bestcase; + } + } + + + private float explMIN(HexBoard position, int depth){ + 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 = 1.0f; + HexPly bestcasemove; + HexPly testmove; + for (int i=0; i= 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); + } 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) { + if (!(board instanceof HexBoard)) { + throw new IllegalArgumentException("Ce joueur attend un HexBoard."); + } + HexBoard hb = (HexBoard) board; + float bestcase; + if(hb.getCurrentPlayer()==Player.PLAYER1){ + bestcase = explMAXAB(hb, 0, -1.0f, 1.0f); + } else { + bestcase = explMINAB(hb, 0, -1.0f, 1.0f); + } + return this.bestmove; + } + + @Override + public Result run(){ + while(!simCurrentBoard.isGameOver()) { + AbstractGamePlayer player = simmapPlayers.get(simCurrentBoard.getCurrentPlayer()); + IBoard board = simCurrentBoard.safeCopy(); + AbstractPly ply = GiveBestMove(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(); + } + + +}