feat!: refonte du projet, bug niveau initialisation

This commit is contained in:
2025-12-18 19:32:41 +01:00
parent 748ae70207
commit d210eedb5a
12 changed files with 597 additions and 722 deletions

View File

@@ -1,285 +1,160 @@
package model;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import model.car.Car;
import model.map.Map;
import visual.*;
/**
* La classe {@link Game} représente le moteur principal du jeu.
* <p>
* Elle contient le modèle (les voitures, l'état du jeu, la carte),
* la vue (Track, Dashboard, Rankboard) et la logique de contrôle
* (pause, boucle de jeu, observateurs).
* </p>
*/
public class Game {
@FunctionalInterface
/**
* L'interface utilisée pour Game.
*/
public static interface Observer {
/**
*
* @return true si la fonction s'est bien passé sinon false (le programme va se
* stopper)
* @return si False le programme s'arrete, sinon il continue
*/
public boolean apply();
}
/**
* {@link VisualInfo} est un enregistrement (record) qui décrit une vue
* graphique
* à afficher dans le jeu (par exemple un tableau de bord, une piste ou un
* classement).
* <p>
* Chaque instance contient toutes les informations nécessaires pour créer et
* positionner
* cette vue à lécran.
* </p>
*
* @param type le type concret de la vue (classe héritant de {@link Game})
* @param title le titre de la fenêtre ou du panneau associé
* @param width la largeur en pixels de la vue
* @param height la hauteur en pixels de la vue
* @param x la position horizontale (en pixels) de la vue sur lécran
* @param y la position verticale (en pixels) de la vue sur lécran
*/
private record VisualInfo(Class<? extends Game> type,
String title, int width, int height, int x, int y) {
}
public static class GameFactory {
public static Map defaultMap() {
return Map.fromInts(new Integer[][] {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, -1, -1, 5, 0 },
{ 0, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 0, 0, -1, 0, 0, -1, 0 },
{ 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0 },
{ 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0 },
{ 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, -1, -1, 2, 0, 0, -1, 0 },
{ 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0 },
{ 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0 },
{ 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -3, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
});
}
public static Game defaultGame(Map map) {
return new Game.Builder()
.addCar("Voiture à LUWIK", Color.BLUE)
.addCar("Voiture à CHARAZADE", Color.PINK)
.addCar("Voiture de UPEC", Color.RED)
.addVisual(Dashboard.class, "Voiture à LUWIK", 300, 200, 1000, 0)
.addVisual(Dashboard.class, "Voiture à CHARAZADE", 300, 200, 1000, 200)
.addVisual(Dashboard.class, "Voiture de UPEC", 300, 200, 1000, 400)
.addVisual(Track.class, "Piste Formule 1", 1000, 500, 1, 1)
.addVisual(Rankboard.class, "Score", 200, 200, 0, 510)
.setTime(1000)
.setMap(map)
.build();
}
}
/**
* Builder pour créer une instance de {@link Game} de façon fluide.
* <p>
* Permet d'ajouter des voitures, de définir la carte, l'état initial et
* le temps entre les étapes du jeu avant de construire l'objet final.
* </p>
*/
public static class Builder {
/** Liste des voitures à créer pour le jeu */
private ArrayList<CarInfo> cars = new ArrayList<>();
/** Liste des voitures à créer pour le jeu */
private ArrayList<VisualInfo> visuals = new ArrayList<>();
/** État initial du jeu */
private Car.State state = Car.State.NORMAL;
/** Temps entre chaque step du jeu */
private int time = 1000;
/** Carte sur laquelle se déroule le jeu */
private final List<Game.Observer> OBSERVERS = new ArrayList<>();
private int time = 500;
private Map map = null;
/** Ajoute une voiture à la liste */
public Builder addCar(String name, Color color) {
cars.add(new CarInfo(name, color));
public Builder car(Car car) {
this.OBSERVERS.add(car);
return this;
}
/** Supprime une voiture de la liste */
public Builder removeCar(String name, Color color) {
cars.forEach((car) -> {
if (car.name == name && car.color == color)
cars.remove(car);
});
return this;
}
/** Définit la carte du jeu */
public Builder setMap(Map map) {
public Builder map(Map map) {
this.map = map;
return this;
}
/** Définit l'état initial du jeu */
public Builder setState(Car.State s) {
this.state = s;
return this;
}
/** Définit le temps entre chaque étape du jeu */
public Builder setTime(int time) {
public Builder time(int time) {
this.time = time;
return this;
}
public Builder addVisual(Class<? extends Game> type, String title,
int width, int height, int x, int y) {
visuals.add(new VisualInfo(type, title, width, height, x, y));
public Builder rankboard(String title, int width,
int height, int x, int y) {
this.OBSERVERS.add(new Rankboard(null, title, width, height, x, y));
return this;
}
private void buildVisual(Game game) {
for (VisualInfo visual : visuals) {
Game view = null;
if (visual.type == Rankboard.class) {
view = new Rankboard(game, visual.title, visual.width,
visual.height, visual.x, visual.y);
} else if (visual.type == Track.class) {
view = new Track(game, visual.title, visual.width,
visual.height, visual.x, visual.y);
} else if (visual.type == Dashboard.class) {
Car car = game.getCars().stream()
.filter((e) -> visual.title.equals(e.getName()))
.findFirst()
.orElseThrow();
view = new Dashboard(game,
car,
visual.title, visual.width,
visual.height, visual.x, visual.y);
}
if (view != null)
game.addObserver(view);
}
public Builder track(String title, int width,
int height, int x, int y) {
this.OBSERVERS.add(new Track(null, title, width, height, x, y));
return this;
}
public Builder dashboard(Car car, String title, int width,
int height, int x, int y) {
this.OBSERVERS.add(new Dashboard(null, car, title, width, height, x, y));
return this;
}
public Builder dashboards(int width, int height, int x, int y) {
List<Car> cars = OBSERVERS.stream()
.filter(o -> o instanceof Car)
.map(o -> (Car) o)
.toList();
int index = 0;
for (Car car : cars) {
dashboard(car, car.getName(), width, height, x, y + (height * index++));
}
return this;
}
/**
* Construit l'instance de {@link Game} avec les paramètres définis.
* <p>
* Si la carte n'a pas été définie, le programme s'arrête.
* </p>
*/
public Game build() {
if (map == null) {
System.err.println("Vous devez définir une carte avant de construire le jeu !");
System.exit(1);
Game game = new Game();
for (Game.Observer observer : this.OBSERVERS) {
switch (observer) {
case GameView gameView -> {
gameView.setGame(game);
}
case Car car -> {
car.setMap(map);
}
default -> {
}
}
}
Game game = new Game(map, cars, state, time);
buildVisual(game);
game.map = this.map;
game.time = this.time;
game.observers = this.OBSERVERS;
return game;
}
}
private final Map map;
/** État du jeu (par exemple, positions, carburant) */
private final Car.State state;
/** Temps entre chaque étape du jeu en millisecondes */
private final int time;
private Map map;
private int time;
private List<Game.Observer> observers;
/** Liste des voitures du jeu */
private final ArrayList<Car> cars = new ArrayList<>();
/** Liste des observateurs pour la mise à jour des vues */
private final ArrayList<Observer> obs = new ArrayList<>();
private boolean isPaused;
/** Indique si le jeu est en pause */
private boolean isPaused = false;
private Game() {
}
/**
* Constructeur principal.
*
* @param map carte du jeu
* @param carInfos liste des informations des voitures à créer
* @param state état initial du jeu
* @param time temps entre chaque étape du jeu
*/
public Game(Map map, ArrayList<CarInfo> carInfos, Car.State state, int time) {
private Game(Map map, int time, List<Game.Observer> observer) {
this.map = map;
this.state = state;
this.time = time;
init(carInfos);
this.observers = observer;
}
/**
* Initialise le jeu en créant les vues et les objets {@link Car}.
* <p>
* Chaque voiture obtient un Dashboard, la piste est affichée avec Track,
* et le Rankboard est créé pour afficher le classement.
* </p>
*
* @param carInfos liste des informations des voitures
* @return l'instance de Game
*/
private Game init(ArrayList<CarInfo> carInfos) {
// Création de chaque voiture avec son Dashboard
for (CarInfo ci : carInfos)
cars.add(new Car(ci.name, ci.color, map)
.setState(state));
return this;
}
/**
* Vérifie si le jeu est terminé.
* <p>
* Le jeu est fini si au moins une voiture a épuisé son carburant.
* </p>
*
* @return true si le jeu est terminé
*/
private boolean isFinish() {
for (Car car : cars) {
if (car.getFuel() == 0)
return true;
}
return false;
}
/**
* Ajoute un observateur pour recevoir les mises à jour du jeu.
*
* @param o observateur
* @return instance de Game pour chaîner
*/
public Game addObserver(Observer o) {
obs.add(o);
observers.add(o);
return this;
}
/**
* Supprime un observateur.
*
* @param o observateur
* @return instance de Game pour chaîner
*/
public Game remObserver(Observer o) {
obs.remove(o);
public Game removeObserver(Observer o) {
observers.remove(o);
return this;
}
/**
* Bascule l'état de pause du jeu.
* <p>
* Si le jeu était en pause, il est relancé et les threads en attente sont
* notifiés.
* </p>
*
* @return true si le jeu est maintenant en pause
*/
private boolean isFinish() {
for (Game.Observer observer : observers) {
switch (observer) {
case Car car -> {
if (car.getFuel() > 0)
return false;
}
default -> {
}
}
}
return true;
}
public void notifyObservers() {
for (Observer o : observers) {
if (!o.apply()) {
System.exit(0);
}
}
}
public void run() {
try {
while (!isFinish()) {
synchronized (this) {
while (isPaused)
wait();
}
notifyObservers();
Thread.sleep(time);
}
} catch (InterruptedException e) {
e.printStackTrace();
System.exit(1);
}
}
public synchronized boolean togglePause() {
if (isPaused)
notifyAll();
@@ -287,73 +162,17 @@ public class Game {
return isPaused;
}
public void notifyObservers() {
for (Observer o : obs) {
boolean isSuccess = o.apply();
if (!isSuccess) {
System.err.println("Une erreur s'est produite pendant le jeu.");
System.exit(1);
}
}
}
/**
* Exécute un cycle du jeu
* <p>
* Chaque voiture effectue son action, puis les observateurs sont notifiés.
* La boucle attend si le jeu est en pause.
* </p>
*
* @throws InterruptedException si le thread est interrompu pendant wait()
*/
private void step() throws InterruptedException {
for (Car car : cars) {
synchronized (this) {
while (isPaused)
wait();
}
car.run();
notifyObservers();
}
}
/**
* Boucle principale du jeu.
* <p>
* Tant que le jeu n'est pas terminé, on exécute les étapes et on met à jour
* les vues toutes les 'time' millisecondes.
* </p>
*/
public void run() {
while (!isFinish()) {
try {
step();
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Fini!\nVoici le score :");
for (Car c : cars) {
System.out.println(c.getName() + "\t" + c.getScore());
}
System.exit(0);
}
public ArrayList<Car> getCars() {
return cars;
public List<Car> getCars() {
return observers.stream()
.filter(o -> o instanceof Car)
.map(o -> (Car) o)
.toList();
}
public Map getMap() {
return map;
}
public Car.State getState() {
return state;
}
public boolean getPause() {
return isPaused;
}

View File

@@ -30,7 +30,7 @@ public class BasicCar implements Car {
/** Nombre de tours complétés */
private int round = 0;
/** Nombre de cases dans une boucle (doit être > 0) */
private final Map MAP;
private Map map;
/** Carburant restant */
private int fuel = 60;
@@ -46,7 +46,12 @@ public class BasicCar implements Car {
public BasicCar(String name, Color color, Map map) {
this.NAME = name;
this.COLOR = color;
this.MAP = map;
this.map = map;
}
public BasicCar(String name, Color color) {
this.NAME = name;
this.COLOR = color;
}
/**
@@ -77,40 +82,6 @@ public class BasicCar implements Car {
return this;
}
/**
* @return la position actuelle dans la boucle
*/
public int getPosition() {
return pos;
}
/**
* @return le score de la voiture, calculé comme :
* (nombre de tours + progression du tour) × 100
*/
public int getScore() {
return (int) ((round + (float) pos / MAP.getPathSize()) * 100);
}
/**
* @return le nombre de tours complétés
*/
public int getRound() {
return round;
}
/**
* @return le carburant restant
*/
public int getFuel() {
return fuel;
}
/** Retourne l'état courant de la voiture. */
public State getState() {
return state;
}
/**
* Clique sur "Accelerer" : change d'état et retourne un message (si
* nécessaire).
@@ -175,15 +146,15 @@ public class BasicCar implements Car {
for (int i = 0; i < jump; i++) {
pos = state.move(pos, movement);
Point point = MAP.getPath(pos);
Circuit element = MAP.getElement(point.x, point.y);
Point point = map.getPath(pos);
Circuit element = map.getElement(point.x, point.y);
if (hasAccident(element, jump)) {
System.out.println(NAME + " a un\taccident");
setDamage();
return;
}
round = pos / MAP.getPathSize();
round = pos / map.getPathSize();
}
}
@@ -203,6 +174,10 @@ public class BasicCar implements Car {
fuel = 0; // sécurité
}
public boolean hasFinished() {
return 3 < round;
}
/**
* Exécute une "étape" de la voiture : avance d'une position aléatoire et
* consomme du carburant.
@@ -212,11 +187,11 @@ public class BasicCar implements Car {
* </p>
*/
@Override
public void run() {
public boolean apply() {
if (fuel > 0) {
move();
consumeFuel();
}
return !hasFinished();
}
@Override
@@ -225,12 +200,39 @@ public class BasicCar implements Car {
}
/**
* @return la position actuelle (synonyme de getPosition)
* @return la position actuelle dans la boucle
*/
public int getPos() {
public int getPosition() {
return pos;
}
/**
* @return le score de la voiture, calculé comme :
* (nombre de tours + progression du tour) × 100
*/
public int getScore() {
return (int) ((round + (float) pos / map.getPathSize()) * 100);
}
/**
* @return le nombre de tours complétés
*/
public int getRound() {
return round;
}
/**
* @return le carburant restant
*/
public int getFuel() {
return fuel;
}
/** Retourne l'état courant de la voiture. */
public State getState() {
return state;
}
/**
* @return la couleur de la voiture
*/
@@ -244,4 +246,9 @@ public class BasicCar implements Car {
public String getName() {
return NAME;
}
@Override
public void setMap(Map map) {
this.map = map;
}
}

View File

@@ -2,7 +2,10 @@ package model.car;
import java.awt.Color;
public interface Car {
import model.Game.Observer;
import model.map.Map;
public interface Car extends Observer {
public String accelerate();
public String decelerate();
@@ -11,13 +14,21 @@ public interface Car {
public void consumeFuel();
public void run();
public void reverse(boolean active);
public int getPos();
public Color getColor();
public String getName();
public int getPosition();
public int getScore();
public int getRound();
public int getFuel();
public State getState();
public void setMap(Map map);
}

View File

@@ -0,0 +1,85 @@
package model.car;
import java.awt.Color;
import model.map.Map;
public abstract class CarDecorator implements Car {
protected final Car car;
public CarDecorator(Car car) {
this.car = car;
}
@Override
public String accelerate() {
return car.accelerate();
}
@Override
public String decelerate() {
return car.decelerate();
}
@Override
public void move() {
car.move();
}
@Override
public void consumeFuel() {
car.consumeFuel();
}
@Override
public boolean apply() {
boolean response = car.apply();
car.consumeFuel();
return response;
}
@Override
public void reverse(boolean active) {
car.reverse(active);
}
@Override
public Color getColor() {
return car.getColor();
}
@Override
public String getName() {
return car.getName();
}
@Override
public int getPosition() {
return car.getPosition();
}
@Override
public int getScore() {
return car.getScore();
}
@Override
public int getRound() {
return car.getRound();
}
@Override
public int getFuel() {
return car.getFuel();
}
@Override
public State getState() {
return car.getState();
}
@Override
public void setMap(Map map) {
car.setMap(map);
}
}

View File

@@ -1,6 +1,5 @@
package model.car;
import java.awt.Color;
import java.util.Random;
/**
@@ -14,14 +13,12 @@ import java.util.Random;
* => On modifie seulement les actions utilisateur (accelerate/decelerate),
* sans modifier la classe Car.
*/
public class DrunkCar implements Car {
public class DrunkCar extends CarDecorator {
/** Générateur de nombres aléatoires pour simuler la progression */
protected static final Random RANDOM = new Random();
private Car car;
public DrunkCar(Car car) {
this.car = car;
super(car);
}
@Override
@@ -32,44 +29,4 @@ public class DrunkCar implements Car {
car.move();
car.reverse(false);
}
@Override
public void run() {
car.run();
}
@Override
public String accelerate() {
return car.accelerate();
}
@Override
public String decelerate() {
return car.decelerate();
}
@Override
public void consumeFuel() {
car.consumeFuel();
}
@Override
public void reverse(boolean active) {
car.reverse(active);
}
@Override
public int getPos() {
return car.getPos();
}
@Override
public Color getColor() {
return car.getColor();
}
@Override
public String getName() {
return car.getName();
}
}

View File

@@ -10,12 +10,11 @@ import java.awt.Color;
* - Mais elle consomme du carburant seulement 1 fois sur 2
* => donc elle économise du carburant.
*/
public class HybridCar implements Car {
private Car car;
public class HybridCar extends CarDecorator {
private int energy = 100; // énergie batterie (0..100)
public HybridCar(Car car) {
this.car = car;
super(car);
}
/** pour afficher l'énergie dans le Dashboard */
@@ -24,68 +23,25 @@ public class HybridCar implements Car {
}
@Override
public void run() {
// 1) La voiture avance toujours
car.move();
public boolean apply() {
boolean response = car.apply();
// 2) Gestion énergie : elle perd 10% à chaque boucle
energy -= 10;
if (energy < 0)
energy = 0;
// 3) Consommation :
// - si on a encore de l'énergie, on économise le fuel (pas de conso)
// - sinon, on consomme normalement
if (energy == 0) {
if (energy > 0) {
energy -= 10;
} else {
car.consumeFuel();
}
return response;
}
@Override
public String decelerate() {
// 1) On applique le ralentissement normal (State pattern)
String msg = car.decelerate();
// 2) Recharge +5% quand on ralentit
energy += 5;
if (energy > 100)
energy = 100;
if (energy <= 100)
energy += 5;
return msg;
}
@Override
public void consumeFuel() {
car.consumeFuel();
}
@Override
public void move() {
car.move();
}
@Override
public String accelerate() {
return car.accelerate();
}
@Override
public void reverse(boolean active) {
car.reverse(active);
}
@Override
public int getPos() {
return car.getPos();
}
@Override
public Color getColor() {
return car.getColor();
}
@Override
public String getName() {
return car.getName();
}
}

View File

@@ -6,11 +6,9 @@ import java.awt.Color;
* Décorateur Sound :
* affiche un message sonore quand la voiture accélère.
*/
public class SoundCar implements Car {
private Car car;
public class SoundCar extends CarDecorator {
public SoundCar(Car car) {
this.car = car;
super(car);
}
@Override
@@ -18,45 +16,4 @@ public class SoundCar implements Car {
System.out.println("VROOOOM VROOOOOOM");
return car.accelerate();
}
@Override
public void run() {
car.run();
}
@Override
public String decelerate() {
return car.decelerate();
}
@Override
public void move() {
car.move();
}
@Override
public void consumeFuel() {
car.consumeFuel();
}
@Override
public void reverse(boolean active) {
car.reverse(active);
}
@Override
public int getPos() {
return car.getPos();
}
@Override
public Color getColor() {
return car.getColor();
}
@Override
public String getName() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getName'");
}
}