Compare commits
11 Commits
d8ea5cd958
...
legitbugfi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe13b946b1 | ||
|
|
c278b18872 | ||
|
|
e0a2c2642a | ||
|
|
c9e559fe12 | ||
|
|
98c6b4678e | ||
| 05871232bd | |||
| 912675f897 | |||
| 0c8f3b0dd6 | |||
| 9e843fe646 | |||
| fa96aae6e6 | |||
| 22891ae2b6 |
BIN
build/fr/iut_fbleau/GameAPI/AbstractBoard.class
Normal file
BIN
build/fr/iut_fbleau/GameAPI/AbstractBoard.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/GameAPI/AbstractGame.class
Normal file
BIN
build/fr/iut_fbleau/GameAPI/AbstractGame.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/GameAPI/AbstractGamePlayer.class
Normal file
BIN
build/fr/iut_fbleau/GameAPI/AbstractGamePlayer.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/GameAPI/AbstractPly.class
Normal file
BIN
build/fr/iut_fbleau/GameAPI/AbstractPly.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/GameAPI/IBoard.class
Normal file
BIN
build/fr/iut_fbleau/GameAPI/IBoard.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/GameAPI/Player.class
Normal file
BIN
build/fr/iut_fbleau/GameAPI/Player.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/GameAPI/Result.class
Normal file
BIN
build/fr/iut_fbleau/GameAPI/Result.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/Arena.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/Arena.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/ArenaMain.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/ArenaMain.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/HeuristicBot.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/HeuristicBot.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/HexBoard.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/HexBoard.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/HexFrame.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/HexFrame.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/HexMain$1.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/HexMain$1.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/HexMain.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/HexMain.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/HexPanel$1.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/HexPanel$1.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/HexPanel.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/HexPanel.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/HexPly.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/HexPly.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/HexSimMain$Args.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/HexSimMain$Args.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/HexSimMain$Stats.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/HexSimMain$Stats.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/HexSimMain.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/HexSimMain.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/HumanConsolePlayer.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/HumanConsolePlayer.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/MiniMaxBot.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/MiniMaxBot.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/MonteCarloBot.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/MonteCarloBot.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/RandomBot.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/RandomBot.class
Normal file
Binary file not shown.
BIN
build/fr/iut_fbleau/HexGame/Simulation.class
Normal file
BIN
build/fr/iut_fbleau/HexGame/Simulation.class
Normal file
Binary file not shown.
1
javaAPI/.~lock.arena_results.csv#
Normal file
1
javaAPI/.~lock.arena_results.csv#
Normal file
@@ -0,0 +1 @@
|
|||||||
|
,vaisse,salle235-12,06.02.2026 11:29,file:///export/home/an23/vaisse/.config/libreoffice/4;
|
||||||
7
javaAPI/arena_results.csv
Normal file
7
javaAPI/arena_results.csv
Normal file
@@ -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
|
||||||
|
@@ -26,5 +26,6 @@ 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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
64
javaAPI/fr/iut_fbleau/HexGame/Arena.java
Normal file
64
javaAPI/fr/iut_fbleau/HexGame/Arena.java
Normal file
@@ -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<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();
|
||||||
|
}
|
||||||
|
}
|
||||||
19
javaAPI/fr/iut_fbleau/HexGame/ArenaMain.java
Normal file
19
javaAPI/fr/iut_fbleau/HexGame/ArenaMain.java
Normal file
@@ -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, 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
53
javaAPI/fr/iut_fbleau/HexGame/HeuristicBot.java
Normal file
53
javaAPI/fr/iut_fbleau/HexGame/HeuristicBot.java
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,63 +1,154 @@
|
|||||||
package fr.iut_fbleau.HexGame;
|
package fr.iut_fbleau.HexGame;
|
||||||
|
|
||||||
import fr.iut_fbleau.GameAPI.*;
|
import fr.iut_fbleau.GameAPI.*;
|
||||||
import java.util.*;
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plateau du jeu de Hex.
|
* Représente le plateau du jeu de Hex.
|
||||||
*
|
*
|
||||||
* Joueur 1 relie la gauche et la droite.
|
* <h2>Rappel des conditions de victoire</h2>
|
||||||
* Joueur 2 relie le haut et le bas.
|
* <ul>
|
||||||
|
* <li>{@link Player#PLAYER1} gagne s'il existe un chemin de pions connectés
|
||||||
|
* reliant le bord gauche au bord droit.</li>
|
||||||
|
* <li>{@link Player#PLAYER2} gagne s'il existe un chemin de pions connectés
|
||||||
|
* reliant le bord haut au bord bas.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <h2>Idée de l'algorithme de détection de victoire</h2>
|
||||||
|
* On modélise le plateau comme un graphe :
|
||||||
|
* <ul>
|
||||||
|
* <li>Chaque case est un sommet</li>
|
||||||
|
* <li>Deux cases sont connectées si elles sont voisines sur la grille hexagonale (6 voisins)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* Pour tester la victoire d'un joueur, on lance un parcours (DFS/BFS) :
|
||||||
|
* <ol>
|
||||||
|
* <li>On part de toutes les cases du bord de départ qui contiennent un pion du joueur.</li>
|
||||||
|
* <li>On explore tous les pions du joueur connectés à ces cases.</li>
|
||||||
|
* <li>Si on atteint le bord opposé, il existe un chemin : le joueur gagne.</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* Complexité : O(N²) au pire (on visite chaque case au plus une fois).
|
||||||
*/
|
*/
|
||||||
public class HexBoard extends AbstractBoard {
|
public class HexBoard extends AbstractBoard {
|
||||||
|
|
||||||
|
/** Taille du plateau : size x size. */
|
||||||
private final int size;
|
private final int size;
|
||||||
private Player[][] cells;
|
|
||||||
private Deque<AbstractPly> historyLocal;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grille des cases.
|
||||||
|
* Une case vaut :
|
||||||
|
* <ul>
|
||||||
|
* <li>null : case vide</li>
|
||||||
|
* <li>PLAYER1 : pion du joueur 1</li>
|
||||||
|
* <li>PLAYER2 : pion du joueur 2</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
private final Player[][] cells;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offsets des 6 voisins d'une case dans une grille hexagonale.
|
||||||
|
*
|
||||||
|
* Pour une case (r,c), les voisins potentiels sont :
|
||||||
|
* (r-1,c), (r+1,c), (r,c-1), (r,c+1), (r-1,c+1), (r+1,c-1).
|
||||||
|
*/
|
||||||
private static final int[][] NEIGHBORS = {
|
private static final int[][] NEIGHBORS = {
|
||||||
{-1, 0}, {+1, 0},
|
{-1, 0}, {+1, 0},
|
||||||
{ 0, -1}, { 0, +1},
|
{ 0, -1}, { 0, +1},
|
||||||
{-1, +1}, {+1, -1}
|
{-1, +1}, {+1, -1}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Crée un plateau vide avec {@link Player#PLAYER1} qui commence. */
|
||||||
public HexBoard(int size) {
|
public HexBoard(int size) {
|
||||||
super();
|
this(size, Player.PLAYER1);
|
||||||
this.size = size;
|
|
||||||
this.cells = new Player[size][size];
|
|
||||||
this.historyLocal = new ArrayDeque<>();
|
|
||||||
this.currentPlayer = Player.PLAYER1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructeur interne, utile pour {@link #safeCopy()}.
|
||||||
|
* @param size taille du plateau
|
||||||
|
* @param current joueur courant
|
||||||
|
*/
|
||||||
|
private HexBoard(int size, Player current) {
|
||||||
|
super(current, new ArrayDeque<>());
|
||||||
|
if (size <= 0) throw new IllegalArgumentException("size must be > 0");
|
||||||
|
this.size = size;
|
||||||
|
this.cells = new Player[size][size];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return la taille du plateau. */
|
||||||
|
public int getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie si (r,c) est dans le plateau.
|
||||||
|
* @param r ligne (0..size-1)
|
||||||
|
* @param c colonne (0..size-1)
|
||||||
|
*/
|
||||||
private boolean inBounds(int r, int c) {
|
private boolean inBounds(int r, int c) {
|
||||||
return r >= 0 && r < size && c >= 0 && c < size;
|
return r >= 0 && r < size && c >= 0 && c < size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return le contenu d'une case (null si vide). */
|
||||||
private Player getCell(int r, int c) {
|
private Player getCell(int r, int c) {
|
||||||
return cells[r][c];
|
return cells[r][c];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Modifie une case (utilisé par doPly/undoPly). */
|
||||||
private void setCell(int r, int c, Player p) {
|
private void setCell(int r, int c, Player p) {
|
||||||
cells[r][c] = p;
|
cells[r][c] = p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Teste la victoire de PLAYER1 (gauche -> droite).
|
||||||
|
*
|
||||||
|
* <h3>Détails de l'algorithme</h3>
|
||||||
|
* <ol>
|
||||||
|
* <li>On initialise une structure "visited" pour ne pas revisiter les cases.</li>
|
||||||
|
* <li>On met dans une pile toutes les cases du bord gauche (colonne 0)
|
||||||
|
* qui contiennent un pion PLAYER1.</li>
|
||||||
|
* <li>On effectue un DFS :
|
||||||
|
* <ul>
|
||||||
|
* <li>on dépile une case</li>
|
||||||
|
* <li>si elle est sur la colonne size-1 : on a touché le bord droit -> victoire</li>
|
||||||
|
* <li>sinon, on empile tous ses voisins qui sont des pions PLAYER1 et pas encore visités</li>
|
||||||
|
* </ul>
|
||||||
|
* </li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* @return true si PLAYER1 a un chemin gauche->droite, false sinon
|
||||||
|
*/
|
||||||
private boolean hasPlayer1Won() {
|
private boolean hasPlayer1Won() {
|
||||||
boolean[][] visited = new boolean[size][size];
|
boolean[][] visited = new boolean[size][size];
|
||||||
Deque<int[]> stack = new ArrayDeque<>();
|
Deque<int[]> stack = new ArrayDeque<>();
|
||||||
|
|
||||||
|
// 1) points de départ : bord gauche
|
||||||
for (int r = 0; r < size; r++) {
|
for (int r = 0; r < size; r++) {
|
||||||
if (getCell(r, 0) == Player.PLAYER1) {
|
if (getCell(r, 0) == Player.PLAYER1) {
|
||||||
visited[r][0] = true;
|
visited[r][0] = true;
|
||||||
stack.push(new int[]{r, 0});
|
stack.push(new int[]{r, 0});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2) DFS
|
||||||
while (!stack.isEmpty()) {
|
while (!stack.isEmpty()) {
|
||||||
int[] cur = stack.pop();
|
int[] cur = stack.pop();
|
||||||
int cr = cur[0];
|
int cr = cur[0], cc = cur[1];
|
||||||
int cc = cur[1];
|
|
||||||
|
// condition d'arrivée : bord droit
|
||||||
if (cc == size - 1) return true;
|
if (cc == size - 1) return true;
|
||||||
|
|
||||||
|
// explore les 6 voisins
|
||||||
for (int[] d : NEIGHBORS) {
|
for (int[] d : NEIGHBORS) {
|
||||||
int nr = cr + d[0], nc = cc + d[1];
|
int nr = cr + d[0], nc = cc + d[1];
|
||||||
if (inBounds(nr, nc) && !visited[nr][nc] && getCell(nr, nc) == Player.PLAYER1) {
|
if (inBounds(nr, nc)
|
||||||
|
&& !visited[nr][nc]
|
||||||
|
&& getCell(nr, nc) == Player.PLAYER1) {
|
||||||
visited[nr][nc] = true;
|
visited[nr][nc] = true;
|
||||||
stack.push(new int[]{nr, nc});
|
stack.push(new int[]{nr, nc});
|
||||||
}
|
}
|
||||||
@@ -66,23 +157,42 @@ public class HexBoard extends AbstractBoard {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Teste la victoire de PLAYER2 (haut -> bas).
|
||||||
|
*
|
||||||
|
* Même principe que {@link #hasPlayer1Won()} mais :
|
||||||
|
* <ul>
|
||||||
|
* <li>Départ : bord haut (ligne 0)</li>
|
||||||
|
* <li>Arrivée : bord bas (ligne size-1)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @return true si PLAYER2 a un chemin haut->bas, false sinon
|
||||||
|
*/
|
||||||
private boolean hasPlayer2Won() {
|
private boolean hasPlayer2Won() {
|
||||||
boolean[][] visited = new boolean[size][size];
|
boolean[][] visited = new boolean[size][size];
|
||||||
Deque<int[]> stack = new ArrayDeque<>();
|
Deque<int[]> stack = new ArrayDeque<>();
|
||||||
|
|
||||||
|
// points de départ : bord haut
|
||||||
for (int c = 0; c < size; c++) {
|
for (int c = 0; c < size; c++) {
|
||||||
if (getCell(0, c) == Player.PLAYER2) {
|
if (getCell(0, c) == Player.PLAYER2) {
|
||||||
visited[0][c] = true;
|
visited[0][c] = true;
|
||||||
stack.push(new int[]{0, c});
|
stack.push(new int[]{0, c});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DFS
|
||||||
while (!stack.isEmpty()) {
|
while (!stack.isEmpty()) {
|
||||||
int[] cur = stack.pop();
|
int[] cur = stack.pop();
|
||||||
int cr = cur[0];
|
int cr = cur[0], cc = cur[1];
|
||||||
int cc = cur[1];
|
|
||||||
|
// condition d'arrivée : bord bas
|
||||||
if (cr == size - 1) return true;
|
if (cr == size - 1) return true;
|
||||||
|
|
||||||
for (int[] d : NEIGHBORS) {
|
for (int[] d : NEIGHBORS) {
|
||||||
int nr = cr + d[0], nc = cc + d[1];
|
int nr = cr + d[0], nc = cc + d[1];
|
||||||
if (inBounds(nr, nc) && !visited[nr][nc] && getCell(nr, nc) == Player.PLAYER2) {
|
if (inBounds(nr, nc)
|
||||||
|
&& !visited[nr][nc]
|
||||||
|
&& getCell(nr, nc) == Player.PLAYER2) {
|
||||||
visited[nr][nc] = true;
|
visited[nr][nc] = true;
|
||||||
stack.push(new int[]{nr, nc});
|
stack.push(new int[]{nr, nc});
|
||||||
}
|
}
|
||||||
@@ -98,18 +208,50 @@ public class HexBoard extends AbstractBoard {
|
|||||||
int r = hp.getRow(), c = hp.getCol();
|
int r = hp.getRow(), c = hp.getCol();
|
||||||
return inBounds(r, c)
|
return inBounds(r, c)
|
||||||
&& getCell(r, c) == null
|
&& getCell(r, c) == null
|
||||||
&& hp.getPlayer() == this.getCurrentPlayer();
|
&& hp.getPlayer() == getCurrentPlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Teste si un coup est immédiatement gagnant.
|
||||||
|
*
|
||||||
|
* On joue le coup, on teste la victoire, puis on annule le coup.
|
||||||
|
* Cela permet d'évaluer un coup sans modifier définitivement l'état du plateau.
|
||||||
|
*
|
||||||
|
* @param move coup à tester
|
||||||
|
* @return true si après ce coup le joueur a gagné, false sinon
|
||||||
|
*/
|
||||||
|
public boolean isWinningMove(AbstractPly move) {
|
||||||
|
if (!isLegal(move)) return false;
|
||||||
|
Player p = move.getPlayer();
|
||||||
|
|
||||||
|
doPly(move);
|
||||||
|
boolean winNow = (p == Player.PLAYER1) ? hasPlayer1Won() : hasPlayer2Won();
|
||||||
|
undoPly();
|
||||||
|
|
||||||
|
return winNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doPly(AbstractPly move) {
|
public void doPly(AbstractPly move) {
|
||||||
if (!(move instanceof HexPly))
|
if (!(move instanceof HexPly)) {
|
||||||
throw new IllegalArgumentException("Coup invalide: " + move);
|
throw new IllegalArgumentException("Coup invalide: " + move);
|
||||||
|
}
|
||||||
|
if (!isLegal(move)) {
|
||||||
|
throw new IllegalStateException("Coup illégal: " + move);
|
||||||
|
}
|
||||||
|
|
||||||
HexPly hp = (HexPly) move;
|
HexPly hp = (HexPly) move;
|
||||||
if (!isLegal(hp))
|
|
||||||
throw new IllegalStateException("Coup illégal: " + hp);
|
|
||||||
setCell(hp.getRow(), hp.getCol(), hp.getPlayer());
|
setCell(hp.getRow(), hp.getCol(), hp.getPlayer());
|
||||||
historyLocal.push(hp);
|
addPlyToHistory(move);
|
||||||
|
setNextPlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void undoPly() {
|
||||||
|
AbstractPly last = removePlyFromHistory();
|
||||||
|
HexPly hp = (HexPly) last;
|
||||||
|
|
||||||
|
setCell(hp.getRow(), hp.getCol(), null);
|
||||||
setNextPlayer();
|
setNextPlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,46 +262,32 @@ public class HexBoard extends AbstractBoard {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result getResult() {
|
public Result getResult() {
|
||||||
if (hasPlayer1Won()) return Result.WIN;
|
if (!isGameOver()) return null;
|
||||||
if (hasPlayer2Won()) return Result.LOSS;
|
if (hasPlayer1Won()) return Result.WIN; // du point de vue PLAYER1
|
||||||
return Result.DRAW;
|
return Result.LOSS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<AbstractPly> getPlies() {
|
public Iterator<AbstractPly> iterator() {
|
||||||
Player me = this.getCurrentPlayer();
|
Player me = getCurrentPlayer();
|
||||||
List<AbstractPly> moves = new ArrayList<>();
|
List<AbstractPly> moves = new ArrayList<>();
|
||||||
|
|
||||||
for (int r = 0; r < size; r++) {
|
for (int r = 0; r < size; r++) {
|
||||||
for (int c = 0; c < size; c++) {
|
for (int c = 0; c < size; c++) {
|
||||||
if (getCell(r, c) == null) moves.add(new HexPly(me, r, c));
|
if (getCell(r, c) == null) {
|
||||||
|
moves.add(new HexPly(me, r, c));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return moves.iterator();
|
return moves.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterator<AbstractPly> getHistory() {
|
|
||||||
return historyLocal.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void undoLastPly() {
|
|
||||||
if (historyLocal.isEmpty()) return;
|
|
||||||
HexPly last = (HexPly) historyLocal.pop();
|
|
||||||
setCell(last.getRow(), last.getCol(), null);
|
|
||||||
this.currentPlayer = last.getPlayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBoard safeCopy() {
|
public IBoard safeCopy() {
|
||||||
HexBoard copy = new HexBoard(this.size);
|
HexBoard copy = new HexBoard(this.size, this.getCurrentPlayer());
|
||||||
copy.currentPlayer = this.currentPlayer;
|
|
||||||
for (int r = 0; r < size; r++) {
|
for (int r = 0; r < size; r++) {
|
||||||
for (int c = 0; c < size; c++) {
|
System.arraycopy(this.cells[r], 0, copy.cells[r], 0, size);
|
||||||
copy.cells[r][c] = this.cells[r][c];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
copy.historyLocal = new ArrayDeque<>(this.historyLocal);
|
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +309,9 @@ public class HexBoard extends AbstractBoard {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSize() {
|
|
||||||
return size;
|
public Player getCellPlayer(int r, int c) {
|
||||||
|
return cells[r][c];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
52
javaAPI/fr/iut_fbleau/HexGame/HexFrame.java
Normal file
52
javaAPI/fr/iut_fbleau/HexGame/HexFrame.java
Normal file
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
javaAPI/fr/iut_fbleau/HexGame/HexMain.java
Normal file
40
javaAPI/fr/iut_fbleau/HexGame/HexMain.java
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package fr.iut_fbleau.HexGame;
|
||||||
|
|
||||||
|
import fr.iut_fbleau.GameAPI.*;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lancement d'une partie de Hex en console.
|
||||||
|
*/
|
||||||
|
public class HexMain {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
int size = 7;
|
||||||
|
if (args.length >= 1) {
|
||||||
|
try { size = Integer.parseInt(args[0]); } catch (NumberFormatException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
HexBoard board = new HexBoard(size);
|
||||||
|
|
||||||
|
Scanner sc = new Scanner(System.in);
|
||||||
|
Result res;
|
||||||
|
EnumMap<Player, AbstractGamePlayer> players = new EnumMap<>(Player.class);
|
||||||
|
players.put(Player.PLAYER1, new HumanConsolePlayer(Player.PLAYER1, sc));
|
||||||
|
players.put(Player.PLAYER2, new HumanConsolePlayer(Player.PLAYER2, sc));
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
sc.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
166
javaAPI/fr/iut_fbleau/HexGame/HexPanel.java
Normal file
166
javaAPI/fr/iut_fbleau/HexGame/HexPanel.java
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
217
javaAPI/fr/iut_fbleau/HexGame/HexSimMain.java
Normal file
217
javaAPI/fr/iut_fbleau/HexGame/HexSimMain.java
Normal file
@@ -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).</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);
|
||||||
|
}
|
||||||
|
}
|
||||||
72
javaAPI/fr/iut_fbleau/HexGame/HumanConsolePlayer.java
Normal file
72
javaAPI/fr/iut_fbleau/HexGame/HumanConsolePlayer.java
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package fr.iut_fbleau.HexGame;
|
||||||
|
|
||||||
|
import fr.iut_fbleau.GameAPI.*;
|
||||||
|
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Joueur humain en console.
|
||||||
|
*
|
||||||
|
* Format attendu : "row col" (indices à partir de 0).
|
||||||
|
*/
|
||||||
|
public class HumanConsolePlayer extends AbstractGamePlayer {
|
||||||
|
|
||||||
|
private final Scanner in;
|
||||||
|
|
||||||
|
public HumanConsolePlayer(Player me, Scanner in) {
|
||||||
|
super(me);
|
||||||
|
this.in = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean jesuisMinimax(){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AbstractPly giveYourMove(IBoard board) {
|
||||||
|
if (!(board instanceof HexBoard)) {
|
||||||
|
throw new IllegalArgumentException("Ce joueur attend un HexBoard.");
|
||||||
|
}
|
||||||
|
HexBoard hb = (HexBoard) board;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
System.out.println(hb);
|
||||||
|
System.out.print("Joueur " + board.getCurrentPlayer() + " - entrez un coup (row col) : ");
|
||||||
|
|
||||||
|
String line = in.nextLine().trim();
|
||||||
|
if (line.equalsIgnoreCase("quit") || line.equalsIgnoreCase("exit")) {
|
||||||
|
throw new IllegalStateException("Partie interrompue par l'utilisateur.");
|
||||||
|
}
|
||||||
|
if (line.equalsIgnoreCase("help")) {
|
||||||
|
System.out.println("Entrez deux entiers : row col (0 <= row,col < " + hb.getSize() + ")");
|
||||||
|
System.out.println("Commandes: help, quit");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] parts = line.split("\\s+");
|
||||||
|
if (parts.length != 2) {
|
||||||
|
System.out.println("Format invalide. Exemple: 3 4");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
int r = Integer.parseInt(parts[0]);
|
||||||
|
int c = Integer.parseInt(parts[1]);
|
||||||
|
HexPly ply = new HexPly(board.getCurrentPlayer(), r, c);
|
||||||
|
|
||||||
|
if (!hb.isLegal(ply)) {
|
||||||
|
System.out.println("Coup illégal (case occupée / hors plateau / mauvais joueur). Réessayez.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hb.isWinningMove(ply)) {
|
||||||
|
System.out.println("Coup gagnant !");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ply;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
System.out.println("Veuillez entrer deux entiers.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
93
javaAPI/fr/iut_fbleau/HexGame/MiniMaxBot.java
Normal file
93
javaAPI/fr/iut_fbleau/HexGame/MiniMaxBot.java
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
javaAPI/fr/iut_fbleau/HexGame/MonteCarloBot.java
Normal file
63
javaAPI/fr/iut_fbleau/HexGame/MonteCarloBot.java
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
javaAPI/fr/iut_fbleau/HexGame/RandomBot.java
Normal file
44
javaAPI/fr/iut_fbleau/HexGame/RandomBot.java
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
293
javaAPI/fr/iut_fbleau/HexGame/Simulation.java
Normal file
293
javaAPI/fr/iut_fbleau/HexGame/Simulation.java
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
Normal file
10001
results.csv
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user