package fr.iut_fbleau.HexGame; import fr.iut_fbleau.GameAPI.*; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.Iterator; import java.util.List; /** * Représente le plateau du jeu de Hex. * *

Rappel des conditions de victoire

* * *

Idée de l'algorithme de détection de victoire

* On modélise le plateau comme un graphe : * * * Pour tester la victoire d'un joueur, on lance un parcours (DFS/BFS) : *
    *
  1. On part de toutes les cases du bord de départ qui contiennent un pion du joueur.
  2. *
  3. On explore tous les pions du joueur connectés à ces cases.
  4. *
  5. Si on atteint le bord opposé, il existe un chemin : le joueur gagne.
  6. *
* * 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; /** * Grille des cases. * Une case vaut : * */ 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} }; /** Crée un plateau vide avec {@link Player#PLAYER1} qui commence. */ public HexBoard(int size) { 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

*
    *
  1. On initialise une structure "visited" pour ne pas revisiter les cases.
  2. *
  3. On met dans une pile toutes les cases du bord gauche (colonne 0) * qui contiennent un pion PLAYER1.
  4. *
  5. On effectue un DFS : * *
  6. *
* * @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], 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) { visited[nr][nc] = true; stack.push(new int[]{nr, nc}); } } } return false; } /** * Teste la victoire de PLAYER2 (haut -> bas). * * Même principe que {@link #hasPlayer1Won()} mais : * * * @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], 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) { visited[nr][nc] = true; stack.push(new int[]{nr, nc}); } } } return false; } @Override public boolean isLegal(AbstractPly move) { if (!(move instanceof HexPly)) return false; HexPly hp = (HexPly) move; int r = hp.getRow(), c = hp.getCol(); return inBounds(r, c) && getCell(r, c) == null && 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)) { throw new IllegalArgumentException("Coup invalide: " + move); } if (!isLegal(move)) { throw new IllegalStateException("Coup illégal: " + move); } HexPly hp = (HexPly) move; setCell(hp.getRow(), hp.getCol(), hp.getPlayer()); addPlyToHistory(move); setNextPlayer(); } @Override public void undoPly() { AbstractPly last = removePlyFromHistory(); HexPly hp = (HexPly) last; setCell(hp.getRow(), hp.getCol(), null); setNextPlayer(); } @Override public boolean isGameOver() { return hasPlayer1Won() || hasPlayer2Won(); } @Override public Result getResult() { if (!isGameOver()) return null; if (hasPlayer1Won()) return Result.WIN; // du point de vue PLAYER1 return Result.LOSS; } @Override 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)); } } } return moves.iterator(); } @Override public IBoard safeCopy() { HexBoard copy = new HexBoard(this.size, this.getCurrentPlayer()); for (int r = 0; r < size; r++) { System.arraycopy(this.cells[r], 0, copy.cells[r], 0, size); } return copy; } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (int r = 0; r < size; r++) { for (int k = 0; k < r; k++) sb.append(" "); for (int c = 0; c < size; c++) { Player p = getCell(r, c); char ch = '.'; if (p == Player.PLAYER1) ch = '1'; else if (p == Player.PLAYER2) ch = '2'; sb.append(ch).append(" "); } sb.append("\n"); } sb.append("Current player: ").append(getCurrentPlayer()).append("\n"); return sb.toString(); } }