Files
archived-L3-racing-game/src/Map.java
2025-12-11 16:05:45 +01:00

437 lines
12 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import java.awt.Point;
import java.util.ArrayList;
import java.util.function.Function;
/**
* <p>
* <code>Map</code> représente le circuit de course.
* </p>
* <p>
* Cette classe contient:
* </p>
* <ul>
* <li>Un tableau 2D de Circuit.Cell</li>
* <li>Une liste de Points qui représente le chemin pour la voiture entre
* <code>START</code> et <code>FINISH</code></li>
* </ul>
*/
public class Map {
/**
* Représente une cellule du circuit.
* <p>
* Chaque cellule possède un type ({@link Cell}) etéventuellement une valeur
* numérique (par exemple pour indiquer une intensité, une vitesse, ou un
* identifiant de route).
* </p>
*/
public static class Circuit {
/**
* <code>Cell</code> est un enum
* représentant les différents types de
* cases qui composent le circuit de
* course.
*/
public static enum Cell {
/**
* Case hors piste, non
* praticable par les
* voitures.
*/
EMPTY,
/**
* Case de route normale
* sur laquelle les voitures
* peuvent circuler.
*/
ROAD,
/**
* Case correspondant à la
* ligne de départ du circuit.
*/
START,
/**
* Case correspondant à la
* ligne d'arrivée du circuit.
*/
FINISH,
/**
* Case de route jaune, plus
* d'information sur le
* livrable 2
*/
YROAD;
}
/** Type de la cellule (vide, route, départ, arrivée, etc.) */
private final Cell type;
/** Valeur associée à la cellule (optionnelle, dépend du type) */
private int value;
/**
* Construit une cellule avec un type défini et une valeur par défaut
* égale à lindice ordinal du type.
*
* @param type le type de la cellule
*/
public Circuit(Cell type) {
this.type = type;
this.value = type.ordinal();
}
/**
* Construit une cellule avec un type et une valeur spécifique.
*
* @param type le type de la cellule
* @param value la valeur associée à la cellule
*/
public Circuit(Cell type, int value) {
this.type = type;
this.value = value;
}
/**
* Modifie la valeur associée à la cellule.
*
* @param value la nouvelle valeur
* @return cette même instance (pour chaînage fluide)
*/
public Circuit setValue(int value) {
this.value = value;
return this;
}
/**
* @return le type de la cellule
*/
public Cell getType() {
return type;
}
/**
* @return la valeur associée à la cellule
*/
public int getValue() {
return value;
}
/**
* Vérifie si la cellule est une route (ROAD ou YROAD).
*
* @return vrai si la cellule représente une route
*/
public boolean isRoad() {
return type == Cell.ROAD || type == Cell.YROAD;
}
public boolean isYRoad() {
return type == Cell.YROAD;
}
/**
* Vérifie si la cellule est un point de départ.
*
* @return vrai si la cellule représente le départ
*/
public boolean isStart() {
return type == Cell.START;
}
/**
* Vérifie si la cellule est un point darrivée.
*
* @return vrai si la cellule représente la fin du circuit
*/
public boolean isFinish() {
return type == Cell.FINISH;
}
}
/**
* Tableau 2D représentant le circuit.
* Chaque élément est une instance de {@link Circuit}.
*/
private Circuit[][] map;
/**
* Liste des coordonnées représentant le chemin du START au FINISH.
*/
private ArrayList<Point> pathMap;
private int width, height;
/**
* Crée une nouvelle instance de Map à partir d'un tableau générique
* et d'une fonction de transformation.
*
* @param <T> Type des éléments du tableau source
* @param fn Fonction qui transforme un élément T en Circuit
* @param array Tableau 2D d'éléments T
* @return Une nouvelle instance de <code>Map</code>
*/
public static <T> Map create(Function<T, Circuit> fn, T[][] map) {
int lenX = map[0].length;
int lenY = map.length;
Circuit[][] newmap = new Circuit[lenY][lenX];
for (int y = 0; y < lenY; y++) {
for (int x = 0; x < lenX; x++) {
newmap[y][x] = fn.apply(map[y][x]);
}
}
return new Map(newmap);
}
/**
* Crée une map à partir d'un tableau d'entiers.
* <ul>
* <li>0 -> EMPTY</li>
* <li>-1 -> ROAD</li>
* <li>-2 -> START</li>
* <li>-3 -> FINISH</li>
* <li>autres -> YROAD avec la valeur associée</li>
* </ul>
*
* @param array Tableau 2D d'entiers
* @return Une nouvelle instance de <code>Map</code>
*/
public static Map fromInts(Integer[][] map) {
return create((i) -> switch (i) {
case 0 -> new Circuit(Circuit.Cell.EMPTY);
case -1 -> new Circuit(Circuit.Cell.ROAD);
case -2 -> new Circuit(Circuit.Cell.START);
case -3 -> new Circuit(Circuit.Cell.FINISH);
default -> new Circuit(Circuit.Cell.YROAD, i);
}, map);
}
/**
* Crée une map à partir d'un tableau de caractères.
* <ul>
* <li>'#' -> ROAD</li>
* <li>'S' -> START</li>
* <li>'F' -> FINISH</li>
* <li>'0'-'9' -> YROAD avec valeur correspondante</li>
* <li>autres -> EMPTY</li>
* </ul>
*
* @param array Tableau 2D de caractères
* @return Une nouvelle instance de <code>Map</code>
*/
public static Map fromChars(Character[][] map) {
return create((i) -> switch (i) {
case '#' -> new Circuit(Circuit.Cell.ROAD);
case 'S' -> new Circuit(Circuit.Cell.START);
case 'F' -> new Circuit(Circuit.Cell.FINISH);
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> new Circuit(Circuit.Cell.YROAD, i - '0');
default -> new Circuit(Circuit.Cell.EMPTY);
}, map);
}
/**
* Constructeur privé utilisé par les méthodes statiques de création.
* Initialise la map et construit le chemin.
* Si la map est invalide ou impossible à résoudre, termine le programme.
*
* @param map Tableau 2D de <code>Circuit</code>
*/
private Map(Circuit[][] map) {
this.map = map;
this.width = map[0].length;
this.height = map.length;
boolean isPossible = this.buildPath();
if (!isPossible) {
System.err.println("La map contient des doublons ou est impossible à finir!");
System.exit(1);
}
}
/**
* Construit le chemin entre START et FINISH.
*
* @return true si un chemin valide existe, false sinon
*/
private boolean buildPath() {
Point[] p = bindPath();
if (p == null)
return false;
Point start = p[0];
Point end = p[1];
this.pathMap = new ArrayList<>();
return followPath(start, end);
}
/**
* Cherche les points START et FINISH sur la map.
*
* @return Un tableau de 2 éléments : {START, FINISH} ou null si la map est
* invalide
*/
private Point[] bindPath() {
Point start = null;
Point end = null;
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[0].length; j++) {
switch (map[i][j].getType()) {
case Circuit.Cell.START:
if (start == null)
start = new Point(j, i);
else
return null;
break;
case Circuit.Cell.FINISH:
if (end == null)
end = new Point(j, i);
else
return null;
break;
default:
break;
}
}
}
if (start == null || end == null)
return null;
return new Point[] { start, end };
}
/**
* Suivi du chemin depuis START jusqu'à FINISH.
* Parcours les cases ROAD et YROAD jusqu'à atteindre FINISH.
*
* @param start Point de départ
* @param end Point d'arrivée
* @return true si un chemin complet a été trouvé, false sinon
*/
private boolean followPath(Point start, Point end) {
// remettre à 0 la liste
pathMap.clear();
// positions
Point current = start;
Point previous = null;
int[][] coord = {
{ 1, 0 }, // haut
{ -1, 0 }, // bas
{ 0, -1 }, // gauche
{ 0, 1 } // droite
};
// sécurité pour éviter les boucles infinie
int step = 0;
int max = map.length * map[0].length;
for (; step < max; step++) {
pathMap.add(current);
if (current.equals(end))
return true;
boolean moved = false;
for (int[] pos : coord) {
int x = current.x + pos[1];
int y = current.y + pos[0];
if ((x >= 0 && map[0].length > x) && (y >= 0 && map.length > y)) {
Point next = new Point(x, y);
if (next.equals(previous))
continue;
Circuit element = getElement(x, y);
Circuit cElement = getElement(current.x, current.y);
if (element.isRoad() || (element.isFinish() && cElement.isRoad())) {
previous = current;
current = next;
moved = true;
break;
}
}
}
// Si il est bloqué
if (!moved)
break;
}
return false;
}
/**
* Retourne l'objet Circuit à la position (x, y).
*
* @param x Coordonnée X
* @param y Coordonnée Y
* @return L'objet <code>Circuit</code>
*/
public Circuit getElement(int x, int y) {
return map[y][x];
}
/**
* Retourne le type de cellule (Circuit.Cell) à la position (x, y).
*
* @param x Coordonnée X
* @param y Coordonnée Y
* @return Le type de cellule <code>Circuit.Cell</code>
*/
public Circuit.Cell getCell(int x, int y) {
return getElement(x, y).getType();
}
/**
* Retourne le point du chemin à l'indice donné.
*
* @param index Indice dans le chemin
* @return Point correspondant
*/
public Point getPath(int i) {
if (i < 0)
return this.pathMap.getFirst();
else if (i >= this.pathMap.size())
return this.pathMap.getLast();
else
return this.pathMap.get(i);
}
/**
* Retourne la taille du chemin trouvé.
*
* @return Nombre de points dans le chemin
*/
public int getPathSize() {
return this.pathMap.size();
}
/**
* Retourne la taille de la map en largueur.
*
* @return Nombre de {@link Cell} sur une largueur
*/
public int getWidth() {
return this.width;
}
/**
* Retourne la taille de la map en longueur.
*
* @return Nombre de {@link Cell} sur une longueur
*/
public int getHeight() {
return this.height;
}
}