Compare commits
3 Commits
f207da0e2b
...
classe_tui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a51eab8df2 | ||
|
|
1d2f169417 | ||
|
|
56727aea9f |
25
HexPly.java
Normal file
25
HexPly.java
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
public class HexPly extends AbstractPly{
|
||||
//attributs
|
||||
private byte y;
|
||||
private byte r;
|
||||
private String color;
|
||||
|
||||
//méthodes
|
||||
public Tuile attemptPlaceTuile(){
|
||||
Tuile t = new Tuile(this.y, this.r, this.color);
|
||||
return t;
|
||||
}
|
||||
|
||||
//constructeur
|
||||
public HexPly(byte y, byte r, Player p){
|
||||
this.y = y;
|
||||
this.r = r;
|
||||
this.joueur = p;
|
||||
if(p == Player.PLAYER1){
|
||||
this.color = "blue";
|
||||
} else {
|
||||
this.color = "red";
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Tuile.java
Normal file
37
Tuile.java
Normal file
@@ -0,0 +1,37 @@
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class Tuile {
|
||||
/*ATTRIBUTS*/
|
||||
private byte y;
|
||||
private byte r;
|
||||
private String color;
|
||||
private LinkedList<Tuile> voisins = new LinkedList<Tuile>();
|
||||
|
||||
/*METHODES*/
|
||||
public byte getY(){
|
||||
return this.y;
|
||||
}
|
||||
|
||||
public byte getR(){
|
||||
return this.r;
|
||||
}
|
||||
|
||||
public String getColor(){
|
||||
return this.color;
|
||||
}
|
||||
|
||||
public void addVoisin(Tuile v){
|
||||
this.voisins.add(v);
|
||||
}
|
||||
|
||||
public LinkedList<Tuile> getVoisins(){
|
||||
return this.voisins;
|
||||
}
|
||||
|
||||
/*CONSTRUCTEUR*/
|
||||
public Tuile(byte y, byte r, String c){
|
||||
this.y = y;
|
||||
this.r = r;
|
||||
this.color = c;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,311 +0,0 @@
|
||||
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.
|
||||
*
|
||||
* <h2>Rappel des conditions de victoire</h2>
|
||||
* <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 {
|
||||
|
||||
/** Taille du plateau : size x size. */
|
||||
private final int size;
|
||||
|
||||
/**
|
||||
* 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 = {
|
||||
{-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).
|
||||
*
|
||||
* <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() {
|
||||
boolean[][] visited = new boolean[size][size];
|
||||
Deque<int[]> 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 :
|
||||
* <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() {
|
||||
boolean[][] visited = new boolean[size][size];
|
||||
Deque<int[]> 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<AbstractPly> iterator() {
|
||||
Player me = getCurrentPlayer();
|
||||
List<AbstractPly> 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();
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
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<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));
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package fr.iut_fbleau.HexGame;
|
||||
|
||||
import fr.iut_fbleau.GameAPI.*;
|
||||
|
||||
/**
|
||||
* Représente un coup dans le jeu de Hex.
|
||||
*/
|
||||
public class HexPly extends AbstractPly {
|
||||
|
||||
private final int row;
|
||||
private final int col;
|
||||
|
||||
public HexPly(Player j, int row, int col) {
|
||||
super(j);
|
||||
this.row = row;
|
||||
this.col = col;
|
||||
}
|
||||
|
||||
public int getRow() {
|
||||
return this.row;
|
||||
}
|
||||
|
||||
public int getCol() {
|
||||
return this.col;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HexPly{player=" + getPlayer() + ", row=" + row + ", col=" + col + "}";
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user