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/build/fr/iut_fbleau/HexGame/HeuristicBot.class b/build/fr/iut_fbleau/HexGame/HeuristicBot.class new file mode 100644 index 0000000..0531221 Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/HeuristicBot.class differ diff --git a/build/fr/iut_fbleau/HexGame/HexBoard.class b/build/fr/iut_fbleau/HexGame/HexBoard.class index 8c58a16..654424d 100644 Binary files a/build/fr/iut_fbleau/HexGame/HexBoard.class and b/build/fr/iut_fbleau/HexGame/HexBoard.class differ diff --git a/build/fr/iut_fbleau/HexGame/HexFrame.class b/build/fr/iut_fbleau/HexGame/HexFrame.class new file mode 100644 index 0000000..29f983f Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/HexFrame.class differ diff --git a/build/fr/iut_fbleau/HexGame/HexMain$1.class b/build/fr/iut_fbleau/HexGame/HexMain$1.class index f572f6f..2cc6a34 100644 Binary files a/build/fr/iut_fbleau/HexGame/HexMain$1.class and b/build/fr/iut_fbleau/HexGame/HexMain$1.class differ diff --git a/build/fr/iut_fbleau/HexGame/HexMain.class b/build/fr/iut_fbleau/HexGame/HexMain.class index 10e072e..4782948 100644 Binary files a/build/fr/iut_fbleau/HexGame/HexMain.class and b/build/fr/iut_fbleau/HexGame/HexMain.class differ diff --git a/build/fr/iut_fbleau/HexGame/HexPanel$1.class b/build/fr/iut_fbleau/HexGame/HexPanel$1.class new file mode 100644 index 0000000..3166fc2 Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/HexPanel$1.class differ diff --git a/build/fr/iut_fbleau/HexGame/HexPanel.class b/build/fr/iut_fbleau/HexGame/HexPanel.class new file mode 100644 index 0000000..78b3803 Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/HexPanel.class differ diff --git a/build/fr/iut_fbleau/HexGame/HexSimMain$Args.class b/build/fr/iut_fbleau/HexGame/HexSimMain$Args.class new file mode 100644 index 0000000..3ac94f9 Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/HexSimMain$Args.class differ diff --git a/build/fr/iut_fbleau/HexGame/HexSimMain$Stats.class b/build/fr/iut_fbleau/HexGame/HexSimMain$Stats.class new file mode 100644 index 0000000..3f1560f Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/HexSimMain$Stats.class differ diff --git a/build/fr/iut_fbleau/HexGame/HexSimMain.class b/build/fr/iut_fbleau/HexGame/HexSimMain.class new file mode 100644 index 0000000..19a1a5b Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/HexSimMain.class differ diff --git a/build/fr/iut_fbleau/HexGame/MiniMaxBot.class b/build/fr/iut_fbleau/HexGame/MiniMaxBot.class new file mode 100644 index 0000000..e57fbb0 Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/MiniMaxBot.class differ diff --git a/build/fr/iut_fbleau/HexGame/MonteCarloBot.class b/build/fr/iut_fbleau/HexGame/MonteCarloBot.class new file mode 100644 index 0000000..d745abd Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/MonteCarloBot.class differ diff --git a/build/fr/iut_fbleau/HexGame/RandomBot.class b/build/fr/iut_fbleau/HexGame/RandomBot.class new file mode 100644 index 0000000..ea379a6 Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/RandomBot.class differ diff --git a/build/fr/iut_fbleau/HexGame/Simulation.class b/build/fr/iut_fbleau/HexGame/Simulation.class new file mode 100644 index 0000000..3c97095 Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/Simulation.class differ diff --git a/javaAPI/arena_results.csv b/javaAPI/arena_results.csv new file mode 100644 index 0000000..6b82172 --- /dev/null +++ b/javaAPI/arena_results.csv @@ -0,0 +1,7 @@ +Bot 1, Bot 2, Winner +RandomBot,MiniMaxBot,WIN +RandomBot,HeuristicBot,WIN +RandomBot,MonteCarloBot,WIN +MiniMaxBot,HeuristicBot,WIN +MiniMaxBot,MonteCarloBot,WIN +HeuristicBot,MonteCarloBot,WIN diff --git a/javaAPI/fr/iut_fbleau/HexGame/Arena.java b/javaAPI/fr/iut_fbleau/HexGame/Arena.java new file mode 100644 index 0000000..379fc11 --- /dev/null +++ b/javaAPI/fr/iut_fbleau/HexGame/Arena.java @@ -0,0 +1,64 @@ +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; + 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 players = new EnumMap<>(Player.class); + players.put(Player.PLAYER1, bot1); + players.put(Player.PLAYER2, bot2); + + Simulation simulation = new Simulation(board, players); + 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..013ac80 --- /dev/null +++ b/javaAPI/fr/iut_fbleau/HexGame/ArenaMain.java @@ -0,0 +1,19 @@ +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, 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..8c9c4c2 --- /dev/null +++ b/javaAPI/fr/iut_fbleau/HexGame/HeuristicBot.java @@ -0,0 +1,49 @@ +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; + //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; + } +} diff --git a/javaAPI/fr/iut_fbleau/HexGame/HexBoard.java b/javaAPI/fr/iut_fbleau/HexGame/HexBoard.java index 3d5c7ca..c8f2646 100644 --- a/javaAPI/fr/iut_fbleau/HexGame/HexBoard.java +++ b/javaAPI/fr/iut_fbleau/HexGame/HexBoard.java @@ -308,4 +308,10 @@ public class HexBoard extends AbstractBoard { sb.append("Current player: ").append(getCurrentPlayer()).append("\n"); return sb.toString(); } + + + public Player getCellPlayer(int r, int c) { + return cells[r][c]; + } + } diff --git a/javaAPI/fr/iut_fbleau/HexGame/HexFrame.java b/javaAPI/fr/iut_fbleau/HexGame/HexFrame.java new file mode 100644 index 0000000..992e8e5 --- /dev/null +++ b/javaAPI/fr/iut_fbleau/HexGame/HexFrame.java @@ -0,0 +1,52 @@ +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"); + } + } +} diff --git a/javaAPI/fr/iut_fbleau/HexGame/HexMain.java b/javaAPI/fr/iut_fbleau/HexGame/HexMain.java index e9d3885..df307da 100644 --- a/javaAPI/fr/iut_fbleau/HexGame/HexMain.java +++ b/javaAPI/fr/iut_fbleau/HexGame/HexMain.java @@ -1,7 +1,6 @@ package fr.iut_fbleau.HexGame; import fr.iut_fbleau.GameAPI.*; - import java.util.EnumMap; import java.util.Scanner; @@ -19,12 +18,19 @@ public class HexMain { HexBoard board = new HexBoard(size); Scanner sc = new Scanner(System.in); + Result res; EnumMap players = new EnumMap<>(Player.class); players.put(Player.PLAYER1, new HumanConsolePlayer(Player.PLAYER1, 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("Résultat (du point de vue de PLAYER1) : " + res); diff --git a/javaAPI/fr/iut_fbleau/HexGame/HexPanel.java b/javaAPI/fr/iut_fbleau/HexGame/HexPanel.java new file mode 100644 index 0000000..ab60f2d --- /dev/null +++ b/javaAPI/fr/iut_fbleau/HexGame/HexPanel.java @@ -0,0 +1,166 @@ +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); + } +} diff --git a/javaAPI/fr/iut_fbleau/HexGame/HexSimMain.java b/javaAPI/fr/iut_fbleau/HexGame/HexSimMain.java new file mode 100644 index 0000000..d314aa7 --- /dev/null +++ b/javaAPI/fr/iut_fbleau/HexGame/HexSimMain.java @@ -0,0 +1,217 @@ +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).

+ * + * 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 { + + 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 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 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); + } +} diff --git a/javaAPI/fr/iut_fbleau/HexGame/MiniMaxBot.java b/javaAPI/fr/iut_fbleau/HexGame/MiniMaxBot.java new file mode 100644 index 0000000..d23ab50 --- /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.getCellPlayer(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..6935120 --- /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 = (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; + } +} diff --git a/javaAPI/fr/iut_fbleau/HexGame/RandomBot.java b/javaAPI/fr/iut_fbleau/HexGame/RandomBot.java new file mode 100644 index 0000000..b670ada --- /dev/null +++ b/javaAPI/fr/iut_fbleau/HexGame/RandomBot.java @@ -0,0 +1,40 @@ +package fr.iut_fbleau.HexGame; + +import fr.iut_fbleau.GameAPI.AbstractGamePlayer; +import fr.iut_fbleau.GameAPI.AbstractPly; +import fr.iut_fbleau.GameAPI.IBoard; +import fr.iut_fbleau.GameAPI.Player; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Random; + +public class RandomBot extends AbstractGamePlayer { + + private final Random rng; + + public RandomBot(Player me, Random rng) { + super(me); + this.rng = rng; + } + + public RandomBot(Player me, long seed) { + this(me, new Random(seed)); + } + + @Override + public AbstractPly giveYourMove(IBoard board) { + List legal = new ArrayList<>(); + Iterator 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())); + } +} diff --git a/javaAPI/fr/iut_fbleau/HexGame/Simulation.java b/javaAPI/fr/iut_fbleau/HexGame/Simulation.java new file mode 100644 index 0000000..6fbc9bd --- /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; +import java.util.Random; + + +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, Player current){ + RandomBot simplay = new RandomBot(current, new Random().nextLong()); + HexBoard simpos = position; + LinkedList ctaken = taken; + HexPly testmove; + float wins = 0; + float losses = 0; + int count = 0; + 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, Player.PLAYER2); + } 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, Player.PLAYER2); + } else { + float bestcase = B; + HexPly bestcasemove; + HexPly testmove; + for (int i=0; i