7 Commits

Author SHA1 Message Date
vaisse
e0a2c2642a c'est 'bon' 2026-02-06 11:13:37 +01:00
vaisse
c9e559fe12 cette fois-ci ce sera la bonne 2026-02-06 11:01:27 +01:00
vaisse
98c6b4678e par pitié 2026-02-06 11:00:03 +01:00
05871232bd Update javaAPI/fr/iut_fbleau/HexGame/HexMain.java 2026-02-06 09:53:00 +01:00
912675f897 Update javaAPI/fr/iut_fbleau/HexGame/Arena.java 2026-02-06 09:31:37 +01:00
0c8f3b0dd6 Delete javaAPI/fr/iut_fbleau.tar 2026-02-06 09:12:30 +01:00
9e843fe646 heuristic + arena + alphabeta 2026-02-05 16:27:10 +01:00
23 changed files with 570 additions and 8 deletions

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

Binary file not shown.

View File

@@ -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<AbstractGamePlayer> 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);
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.run();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -1,7 +1,6 @@
package fr.iut_fbleau.HexGame; package fr.iut_fbleau.HexGame;
import fr.iut_fbleau.GameAPI.*; import fr.iut_fbleau.GameAPI.*;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.Scanner; import java.util.Scanner;
@@ -19,12 +18,19 @@ public class HexMain {
HexBoard board = new HexBoard(size); HexBoard board = new HexBoard(size);
Scanner sc = new Scanner(System.in); Scanner sc = new Scanner(System.in);
Result res;
EnumMap<Player, AbstractGamePlayer> players = new EnumMap<>(Player.class); EnumMap<Player, AbstractGamePlayer> players = new EnumMap<>(Player.class);
players.put(Player.PLAYER1, new HumanConsolePlayer(Player.PLAYER1, sc)); players.put(Player.PLAYER1, new HumanConsolePlayer(Player.PLAYER1, sc));
players.put(Player.PLAYER2, new HumanConsolePlayer(Player.PLAYER2, sc)); players.put(Player.PLAYER2, new HumanConsolePlayer(Player.PLAYER2, sc));
AbstractGame game = new AbstractGame(board, players) {};
Result res = game.run(); if (args.length>=2 && args[1].equals("autoplay")) {
Simulation sim = new Simulation(board, players);
res = sim.run();
} else {
AbstractGame game = new AbstractGame(board, players) {};
res = game.run();
}
System.out.println(board); System.out.println(board);
System.out.println("Résultat (du point de vue de PLAYER1) : " + res); System.out.println("Résultat (du point de vue de PLAYER1) : " + res);

View File

@@ -15,6 +15,19 @@ import java.util.Random;
* java fr.iut_fbleau.HexGame.HexSimMain * 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 10000 --size 7 --seed 123
* java fr.iut_fbleau.HexGame.HexSimMain --games 5000 --size 11 --csv results.csv * 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 { public class HexSimMain {
@@ -170,7 +183,7 @@ public class HexSimMain {
// ex: "7 10000" // ex: "7 10000"
if (isInt(s)) { if (isInt(s)) {
int v = Integer.parseInt(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 a.games = v;
} else { } else {
System.err.println("Unknown arg: " + s); System.err.println("Unknown arg: " + s);

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -10,9 +10,6 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
/**
* Bot non intelligent : joue un coup légal au hasard.
*/
public class RandomBot extends AbstractGamePlayer { public class RandomBot extends AbstractGamePlayer {
private final Random rng; private final Random rng;
@@ -28,7 +25,6 @@ public class RandomBot extends AbstractGamePlayer {
@Override @Override
public AbstractPly giveYourMove(IBoard board) { public AbstractPly giveYourMove(IBoard board) {
// On récupère tous les coups légaux via l'itérateur fourni par le plateau.
List<AbstractPly> legal = new ArrayList<>(); List<AbstractPly> legal = new ArrayList<>();
Iterator<AbstractPly> it = board.iterator(); Iterator<AbstractPly> it = board.iterator();
while (it.hasNext()) { while (it.hasNext()) {

View File

@@ -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<Integer[]> taken = new LinkedList<Integer[]>();
//ATTRIBUTS QUE JE NE VOUDRAIS PAS CRÉER IDÉALEMENT
private IBoard simCurrentBoard;
private EnumMap<Player, AbstractGamePlayer> simmapPlayers;
//CONSTRUCTEUR
public Simulation(IBoard b, EnumMap<Player,AbstractGamePlayer> 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<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){
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 = -1.0f;
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 = explMIN(position, depth+1);
if (val >= 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<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 = explMAX(position, depth+1);
if (val <= bestcase) {
//System.out.println(" MIN 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 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) {
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();
}
}