diff --git a/build/fr/iut_fbleau/GameAPI/AbstractBoard.class b/build/fr/iut_fbleau/GameAPI/AbstractBoard.class
new file mode 100644
index 0000000..7c1f157
Binary files /dev/null and b/build/fr/iut_fbleau/GameAPI/AbstractBoard.class differ
diff --git a/build/fr/iut_fbleau/GameAPI/AbstractGame.class b/build/fr/iut_fbleau/GameAPI/AbstractGame.class
new file mode 100644
index 0000000..b2e2856
Binary files /dev/null and b/build/fr/iut_fbleau/GameAPI/AbstractGame.class differ
diff --git a/build/fr/iut_fbleau/GameAPI/AbstractGamePlayer.class b/build/fr/iut_fbleau/GameAPI/AbstractGamePlayer.class
new file mode 100644
index 0000000..deaa1ac
Binary files /dev/null and b/build/fr/iut_fbleau/GameAPI/AbstractGamePlayer.class differ
diff --git a/build/fr/iut_fbleau/GameAPI/AbstractPly.class b/build/fr/iut_fbleau/GameAPI/AbstractPly.class
new file mode 100644
index 0000000..79018e6
Binary files /dev/null and b/build/fr/iut_fbleau/GameAPI/AbstractPly.class differ
diff --git a/build/fr/iut_fbleau/GameAPI/IBoard.class b/build/fr/iut_fbleau/GameAPI/IBoard.class
new file mode 100644
index 0000000..35a6e98
Binary files /dev/null and b/build/fr/iut_fbleau/GameAPI/IBoard.class differ
diff --git a/build/fr/iut_fbleau/GameAPI/Player.class b/build/fr/iut_fbleau/GameAPI/Player.class
new file mode 100644
index 0000000..6be1739
Binary files /dev/null and b/build/fr/iut_fbleau/GameAPI/Player.class differ
diff --git a/build/fr/iut_fbleau/GameAPI/Result.class b/build/fr/iut_fbleau/GameAPI/Result.class
new file mode 100644
index 0000000..73c27d2
Binary files /dev/null and b/build/fr/iut_fbleau/GameAPI/Result.class differ
diff --git a/build/fr/iut_fbleau/HexGame/HexBoard.class b/build/fr/iut_fbleau/HexGame/HexBoard.class
new file mode 100644
index 0000000..8c58a16
Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/HexBoard.class differ
diff --git a/build/fr/iut_fbleau/HexGame/HexMain$1.class b/build/fr/iut_fbleau/HexGame/HexMain$1.class
new file mode 100644
index 0000000..f572f6f
Binary files /dev/null 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
new file mode 100644
index 0000000..10e072e
Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/HexMain.class differ
diff --git a/build/fr/iut_fbleau/HexGame/HexPly.class b/build/fr/iut_fbleau/HexGame/HexPly.class
new file mode 100644
index 0000000..51bf46d
Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/HexPly.class differ
diff --git a/build/fr/iut_fbleau/HexGame/HumanConsolePlayer.class b/build/fr/iut_fbleau/HexGame/HumanConsolePlayer.class
new file mode 100644
index 0000000..ab4c1f6
Binary files /dev/null and b/build/fr/iut_fbleau/HexGame/HumanConsolePlayer.class differ
diff --git a/javaAPI/fr/iut_fbleau/HexGame/HexBoard.java b/javaAPI/fr/iut_fbleau/HexGame/HexBoard.java
index 9dfd892..3d5c7ca 100644
--- a/javaAPI/fr/iut_fbleau/HexGame/HexBoard.java
+++ b/javaAPI/fr/iut_fbleau/HexGame/HexBoard.java
@@ -1,63 +1,154 @@
package fr.iut_fbleau.HexGame;
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.
- * Joueur 2 relie le haut et le bas.
+ *
Rappel des conditions de victoire
+ *
+ * - {@link Player#PLAYER1} gagne s'il existe un chemin de pions connectés
+ * reliant le bord gauche au bord droit.
+ * - {@link Player#PLAYER2} gagne s'il existe un chemin de pions connectés
+ * reliant le bord haut au bord bas.
+ *
+ *
+ * Idée de l'algorithme de détection de victoire
+ * On modélise le plateau comme un graphe :
+ *
+ * - Chaque case est un sommet
+ * - Deux cases sont connectées si elles sont voisines sur la grille hexagonale (6 voisins)
+ *
+ *
+ * Pour tester la victoire d'un joueur, on lance un parcours (DFS/BFS) :
+ *
+ * - On part de toutes les cases du bord de départ qui contiennent un pion du joueur.
+ * - On explore tous les pions du joueur connectés à ces cases.
+ * - Si on atteint le bord opposé, il existe un chemin : le joueur gagne.
+ *
+ *
+ * Complexité : O(N²) au pire (on visite chaque case au plus une fois).
*/
public class HexBoard extends AbstractBoard {
+ /** Taille du plateau : size x size. */
private final int size;
- private Player[][] cells;
- private Deque historyLocal;
+ /**
+ * Grille des cases.
+ * Une case vaut :
+ *
+ * - null : case vide
+ * - PLAYER1 : pion du joueur 1
+ * - PLAYER2 : pion du joueur 2
+ *
+ */
+ 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 = {
- {-1, 0}, {+1, 0},
- { 0, -1}, { 0, +1},
- {-1, +1}, {+1, -1}
+ {-1, 0}, {+1, 0},
+ { 0, -1}, { 0, +1},
+ {-1, +1}, {+1, -1}
};
+ /** Crée un plateau vide avec {@link Player#PLAYER1} qui commence. */
public HexBoard(int size) {
- super();
- this.size = size;
- this.cells = new Player[size][size];
- this.historyLocal = new ArrayDeque<>();
- this.currentPlayer = Player.PLAYER1;
+ this(size, 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) {
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) {
return cells[r][c];
}
+ /** Modifie une case (utilisé par doPly/undoPly). */
private void setCell(int r, int c, Player p) {
cells[r][c] = p;
}
+ /**
+ * Teste la victoire de PLAYER1 (gauche -> droite).
+ *
+ * Détails de l'algorithme
+ *
+ * - On initialise une structure "visited" pour ne pas revisiter les cases.
+ * - On met dans une pile toutes les cases du bord gauche (colonne 0)
+ * qui contiennent un pion PLAYER1.
+ * - On effectue un DFS :
+ *
+ * - on dépile une case
+ * - si elle est sur la colonne size-1 : on a touché le bord droit -> victoire
+ * - sinon, on empile tous ses voisins qui sont des pions PLAYER1 et pas encore visités
+ *
+ *
+ *
+ *
+ * @return true si PLAYER1 a un chemin gauche->droite, false sinon
+ */
private boolean hasPlayer1Won() {
boolean[][] visited = new boolean[size][size];
Deque stack = new ArrayDeque<>();
+
+ // 1) points de départ : bord gauche
for (int r = 0; r < size; r++) {
if (getCell(r, 0) == Player.PLAYER1) {
visited[r][0] = true;
stack.push(new int[]{r, 0});
}
}
+
+ // 2) DFS
while (!stack.isEmpty()) {
int[] cur = stack.pop();
- int cr = cur[0];
- int cc = cur[1];
+ int cr = cur[0], cc = cur[1];
+
+ // condition d'arrivée : bord droit
if (cc == size - 1) return true;
+
+ // explore les 6 voisins
for (int[] d : NEIGHBORS) {
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;
stack.push(new int[]{nr, nc});
}
@@ -66,23 +157,42 @@ public class HexBoard extends AbstractBoard {
return false;
}
+ /**
+ * Teste la victoire de PLAYER2 (haut -> bas).
+ *
+ * Même principe que {@link #hasPlayer1Won()} mais :
+ *
+ * - Départ : bord haut (ligne 0)
+ * - Arrivée : bord bas (ligne size-1)
+ *
+ *
+ * @return true si PLAYER2 a un chemin haut->bas, false sinon
+ */
private boolean hasPlayer2Won() {
boolean[][] visited = new boolean[size][size];
Deque stack = new ArrayDeque<>();
+
+ // points de départ : bord haut
for (int c = 0; c < size; c++) {
if (getCell(0, c) == Player.PLAYER2) {
visited[0][c] = true;
stack.push(new int[]{0, c});
}
}
+
+ // DFS
while (!stack.isEmpty()) {
int[] cur = stack.pop();
- int cr = cur[0];
- int cc = cur[1];
+ int cr = cur[0], cc = cur[1];
+
+ // condition d'arrivée : bord bas
if (cr == size - 1) return true;
+
for (int[] d : NEIGHBORS) {
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;
stack.push(new int[]{nr, nc});
}
@@ -98,18 +208,50 @@ public class HexBoard extends AbstractBoard {
int r = hp.getRow(), c = hp.getCol();
return inBounds(r, c)
&& 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
public void doPly(AbstractPly move) {
- if (!(move instanceof HexPly))
+ if (!(move instanceof HexPly)) {
throw new IllegalArgumentException("Coup invalide: " + move);
+ }
+ if (!isLegal(move)) {
+ throw new IllegalStateException("Coup illégal: " + move);
+ }
+
HexPly hp = (HexPly) move;
- if (!isLegal(hp))
- throw new IllegalStateException("Coup illégal: " + hp);
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();
}
@@ -120,46 +262,32 @@ public class HexBoard extends AbstractBoard {
@Override
public Result getResult() {
- if (hasPlayer1Won()) return Result.WIN;
- if (hasPlayer2Won()) return Result.LOSS;
- return Result.DRAW;
+ if (!isGameOver()) return null;
+ if (hasPlayer1Won()) return Result.WIN; // du point de vue PLAYER1
+ return Result.LOSS;
}
@Override
- public Iterator getPlies() {
- Player me = this.getCurrentPlayer();
+ public Iterator iterator() {
+ Player me = getCurrentPlayer();
List moves = new ArrayList<>();
+
for (int r = 0; r < size; r++) {
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();
}
- @Override
- public Iterator 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
public IBoard safeCopy() {
- HexBoard copy = new HexBoard(this.size);
- copy.currentPlayer = this.currentPlayer;
+ HexBoard copy = new HexBoard(this.size, this.getCurrentPlayer());
for (int r = 0; r < size; r++) {
- for (int c = 0; c < size; c++) {
- copy.cells[r][c] = this.cells[r][c];
- }
+ System.arraycopy(this.cells[r], 0, copy.cells[r], 0, size);
}
- copy.historyLocal = new ArrayDeque<>(this.historyLocal);
return copy;
}
@@ -180,8 +308,4 @@ public class HexBoard extends AbstractBoard {
sb.append("Current player: ").append(getCurrentPlayer()).append("\n");
return sb.toString();
}
-
- public int getSize() {
- return size;
- }
}
diff --git a/javaAPI/fr/iut_fbleau/HexGame/HexMain.java b/javaAPI/fr/iut_fbleau/HexGame/HexMain.java
new file mode 100644
index 0000000..e9d3885
--- /dev/null
+++ b/javaAPI/fr/iut_fbleau/HexGame/HexMain.java
@@ -0,0 +1,34 @@
+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);
+ 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();
+
+ System.out.println(board);
+ System.out.println("Résultat (du point de vue de PLAYER1) : " + res);
+
+ sc.close();
+ }
+}
diff --git a/javaAPI/fr/iut_fbleau/HexGame/HumanConsolePlayer.java b/javaAPI/fr/iut_fbleau/HexGame/HumanConsolePlayer.java
new file mode 100644
index 0000000..63068fc
--- /dev/null
+++ b/javaAPI/fr/iut_fbleau/HexGame/HumanConsolePlayer.java
@@ -0,0 +1,68 @@
+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;
+ }
+
+ @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.");
+ }
+ }
+ }
+}