Bots Aleatoires + Simu parties + csv de resultats
This commit is contained in:
204
javaAPI/fr/iut_fbleau/HexGame/HexSimMain.java
Normal file
204
javaAPI/fr/iut_fbleau/HexGame/HexSimMain.java
Normal file
@@ -0,0 +1,204 @@
|
||||
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
|
||||
*/
|
||||
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 == 7) 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user