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,12 +1,34 @@
import java.awt.Color;
import java.util.List; import java.util.List;
import model.Game; import model.Game;
import model.car.BasicCar;
import model.car.DrunkCar;
import model.map.Map; import model.map.Map;
public class Main { public class Main {
public static void main(String[] args) throws InterruptedException { public static void main(String[] args) throws InterruptedException {
Map map = Game.GameFactory.defaultMap(); Map map = Map.fromInts(new Integer[][] {
Game game = Game.GameFactory.defaultGame(map); { 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 },
});
Game game = new Game.Builder()
.car(new BasicCar("Luwik", Color.RED))
.car(new DrunkCar(new BasicCar("Charazade", Color.PINK)))
.track("Piste Formule 1", 1000, 500, 1, 1)
.rankboard("Score", 200, 200, 0, 510)
.dashboards(300, 200, 1000, 0)
.map(map)
.build();
game.run(); game.run();
} }
} }

View File

@@ -1,285 +1,160 @@
package model; package model;
import java.awt.Color;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import model.car.Car; import model.car.Car;
import model.map.Map; 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 { public class Game {
@FunctionalInterface @FunctionalInterface
/**
* L'interface utilisée pour Game.
*/
public static interface Observer { public static interface Observer {
/** /**
* *
* @return true si la fonction s'est bien passé sinon false (le programme va se * @return si False le programme s'arrete, sinon il continue
* stopper)
*/ */
public boolean apply(); 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 { public static class Builder {
/** Liste des voitures à créer pour le jeu */ private final List<Game.Observer> OBSERVERS = new ArrayList<>();
private ArrayList<CarInfo> cars = new ArrayList<>(); private int time = 500;
/** 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 Map map = null; private Map map = null;
/** Ajoute une voiture à la liste */ public Builder car(Car car) {
public Builder addCar(String name, Color color) { this.OBSERVERS.add(car);
cars.add(new CarInfo(name, color));
return this; return this;
} }
/** Supprime une voiture de la liste */ public Builder map(Map map) {
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) {
this.map = map; this.map = map;
return this; return this;
} }
/** Définit l'état initial du jeu */ public Builder time(int time) {
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) {
this.time = time; this.time = time;
return this; return this;
} }
public Builder addVisual(Class<? extends Game> type, String title, public Builder rankboard(String title, int width,
int width, int height, int x, int y) { int height, int x, int y) {
visuals.add(new VisualInfo(type, title, width, height, x, y)); this.OBSERVERS.add(new Rankboard(null, title, width, height, x, y));
return this; return this;
} }
private void buildVisual(Game game) { public Builder track(String title, int width,
for (VisualInfo visual : visuals) { int height, int x, int y) {
Game view = null; this.OBSERVERS.add(new Track(null, title, width, height, x, y));
return this;
if (visual.type == Rankboard.class) { }
view = new Rankboard(game, visual.title, visual.width,
visual.height, visual.x, visual.y); public Builder dashboard(Car car, String title, int width,
} else if (visual.type == Track.class) { int height, int x, int y) {
view = new Track(game, visual.title, visual.width, this.OBSERVERS.add(new Dashboard(null, car, title, width, height, x, y));
visual.height, visual.x, visual.y); return this;
} else if (visual.type == Dashboard.class) { }
Car car = game.getCars().stream()
.filter((e) -> visual.title.equals(e.getName())) public Builder dashboards(int width, int height, int x, int y) {
.findFirst() List<Car> cars = OBSERVERS.stream()
.orElseThrow(); .filter(o -> o instanceof Car)
.map(o -> (Car) o)
view = new Dashboard(game, .toList();
car,
visual.title, visual.width, int index = 0;
visual.height, visual.x, visual.y); for (Car car : cars) {
} dashboard(car, car.getName(), width, height, x, y + (height * index++));
if (view != null) }
game.addObserver(view); 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() { public Game build() {
if (map == null) { Game game = new Game();
System.err.println("Vous devez définir une carte avant de construire le jeu !");
System.exit(1); 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); game.map = this.map;
buildVisual(game); game.time = this.time;
game.observers = this.OBSERVERS;
return game; return game;
} }
} }
private final Map map; private Map map;
/** État du jeu (par exemple, positions, carburant) */ private int time;
private final Car.State state; private List<Game.Observer> observers;
/** Temps entre chaque étape du jeu en millisecondes */
private final int time;
/** Liste des voitures du jeu */ private boolean isPaused;
private final ArrayList<Car> cars = new ArrayList<>();
/** Liste des observateurs pour la mise à jour des vues */
private final ArrayList<Observer> obs = new ArrayList<>();
/** Indique si le jeu est en pause */ private Game() {
private boolean isPaused = false; }
/** private Game(Map map, int time, List<Game.Observer> observer) {
* 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) {
this.map = map; this.map = map;
this.state = state;
this.time = time; this.time = time;
this.observers = observer;
init(carInfos);
} }
/**
* 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) { public Game addObserver(Observer o) {
obs.add(o); observers.add(o);
return this; return this;
} }
/** public Game removeObserver(Observer o) {
* Supprime un observateur. observers.remove(o);
*
* @param o observateur
* @return instance de Game pour chaîner
*/
public Game remObserver(Observer o) {
obs.remove(o);
return this; return this;
} }
/** private boolean isFinish() {
* Bascule l'état de pause du jeu. for (Game.Observer observer : observers) {
* <p> switch (observer) {
* Si le jeu était en pause, il est relancé et les threads en attente sont case Car car -> {
* notifiés. if (car.getFuel() > 0)
* </p> return false;
* }
* @return true si le jeu est maintenant en pause 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() { public synchronized boolean togglePause() {
if (isPaused) if (isPaused)
notifyAll(); notifyAll();
@@ -287,73 +162,17 @@ public class Game {
return isPaused; return isPaused;
} }
public void notifyObservers() { public List<Car> getCars() {
for (Observer o : obs) { return observers.stream()
boolean isSuccess = o.apply(); .filter(o -> o instanceof Car)
if (!isSuccess) { .map(o -> (Car) o)
System.err.println("Une erreur s'est produite pendant le jeu."); .toList();
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 Map getMap() { public Map getMap() {
return map; return map;
} }
public Car.State getState() {
return state;
}
public boolean getPause() { public boolean getPause() {
return isPaused; return isPaused;
} }

View File

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

View File

@@ -2,7 +2,10 @@ package model.car;
import java.awt.Color; 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 accelerate();
public String decelerate(); public String decelerate();
@@ -11,13 +14,21 @@ public interface Car {
public void consumeFuel(); public void consumeFuel();
public void run();
public void reverse(boolean active); public void reverse(boolean active);
public int getPos();
public Color getColor(); public Color getColor();
public String getName(); 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; package model.car;
import java.awt.Color;
import java.util.Random; import java.util.Random;
/** /**
@@ -14,14 +13,12 @@ import java.util.Random;
* => On modifie seulement les actions utilisateur (accelerate/decelerate), * => On modifie seulement les actions utilisateur (accelerate/decelerate),
* sans modifier la classe Car. * 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 */ /** Générateur de nombres aléatoires pour simuler la progression */
protected static final Random RANDOM = new Random(); protected static final Random RANDOM = new Random();
private Car car;
public DrunkCar(Car car) { public DrunkCar(Car car) {
this.car = car; super(car);
} }
@Override @Override
@@ -32,44 +29,4 @@ public class DrunkCar implements Car {
car.move(); car.move();
car.reverse(false); 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 * - Mais elle consomme du carburant seulement 1 fois sur 2
* => donc elle économise du carburant. * => donc elle économise du carburant.
*/ */
public class HybridCar implements Car { public class HybridCar extends CarDecorator {
private Car car;
private int energy = 100; // énergie batterie (0..100) private int energy = 100; // énergie batterie (0..100)
public HybridCar(Car car) { public HybridCar(Car car) {
this.car = car; super(car);
} }
/** pour afficher l'énergie dans le Dashboard */ /** pour afficher l'énergie dans le Dashboard */
@@ -24,68 +23,25 @@ public class HybridCar implements Car {
} }
@Override @Override
public void run() { public boolean apply() {
// 1) La voiture avance toujours boolean response = car.apply();
car.move();
// 2) Gestion énergie : elle perd 10% à chaque boucle if (energy > 0) {
energy -= 10; energy -= 10;
if (energy < 0) } else {
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) {
car.consumeFuel(); car.consumeFuel();
} }
return response;
} }
@Override @Override
public String decelerate() { public String decelerate() {
// 1) On applique le ralentissement normal (State pattern)
String msg = car.decelerate(); String msg = car.decelerate();
// 2) Recharge +5% quand on ralentit if (energy <= 100)
energy += 5; energy += 5;
if (energy > 100)
energy = 100;
return msg; 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 : * Décorateur Sound :
* affiche un message sonore quand la voiture accélère. * affiche un message sonore quand la voiture accélère.
*/ */
public class SoundCar implements Car { public class SoundCar extends CarDecorator {
private Car car;
public SoundCar(Car car) { public SoundCar(Car car) {
this.car = car; super(car);
} }
@Override @Override
@@ -18,45 +16,4 @@ public class SoundCar implements Car {
System.out.println("VROOOOM VROOOOOOM"); System.out.println("VROOOOM VROOOOOOM");
return car.accelerate(); 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'");
}
} }

View File

@@ -7,21 +7,21 @@ import javax.swing.JLabel;
import javax.swing.JPanel; import javax.swing.JPanel;
import model.Game; import model.Game;
import model.car.BasicCar; import model.car.Car;
/** /**
* Dashboard représente une vue graphique pour une voiture spécifique. * Dashboard représente une vue graphique pour une voiture spécifique.
* *
* Il affiche : * Il affiche :
* - le carburant restant * - le carburant restant
* - le nombre de tours complétés * - le nombre de tours complétés
* - l'état courant de la voiture * - l'état courant de la voiture
* - un message (info utilisateur) * - un message (info utilisateur)
* *
* Il fournit : * Il fournit :
* - un bouton Pause/Reprise du jeu * - un bouton Pause/Reprise du jeu
* - un bouton Accelerer * - un bouton Accelerer
* - un bouton Rallentir * - un bouton Rallentir
*/ */
public class Dashboard extends GameView { public class Dashboard extends GameView {
@@ -44,7 +44,7 @@ public class Dashboard extends GameView {
private final JButton decelerateButton = new JButton("Rallentir"); private final JButton decelerateButton = new JButton("Rallentir");
/** Voiture associée à ce dashboard */ /** Voiture associée à ce dashboard */
private final BasicCar car; private Car car;
/** /**
* Construit un dashboard pour une voiture donnée. * Construit un dashboard pour une voiture donnée.
@@ -57,13 +57,15 @@ public class Dashboard extends GameView {
* @param x position horizontale de la fenêtre * @param x position horizontale de la fenêtre
* @param y position verticale de la fenêtre * @param y position verticale de la fenêtre
*/ */
public Dashboard(Game game, BasicCar car, String title, int width, int height, int x, int y) { public Dashboard(Game game, Car car, String title, int width, int height, int x, int y) {
super(game, "Dashboard: " + title, width, height, x, y); super(game, "Dashboard: " + title, width, height, x, y);
this.car = car; this.car = car;
if (car != null && game != null)
init();
}
private void init() {
// Fond de la fenêtre = couleur de la voiture // Fond de la fenêtre = couleur de la voiture
frame.setBackground(car.getColor()); frame.setBackground(car.getColor());
@@ -72,7 +74,7 @@ public class Dashboard extends GameView {
centerPanel.add(infoLabel); centerPanel.add(infoLabel);
centerPanel.add(stateLabel); centerPanel.add(stateLabel);
centerPanel.add(messageLabel); centerPanel.add(messageLabel);
//pour rendre le panel transparent // pour rendre le panel transparent
centerPanel.setOpaque(false); centerPanel.setOpaque(false);
// ----- Partie boutons ----- // ----- Partie boutons -----
@@ -80,7 +82,6 @@ public class Dashboard extends GameView {
buttonPanel.add(decelerateButton); buttonPanel.add(decelerateButton);
buttonPanel.add(pauseButton); buttonPanel.add(pauseButton);
buttonPanel.add(accelerateButton); buttonPanel.add(accelerateButton);
setLayout(new BorderLayout()); setLayout(new BorderLayout());
add(centerPanel, BorderLayout.CENTER); add(centerPanel, BorderLayout.CENTER);
@@ -108,25 +109,38 @@ public class Dashboard extends GameView {
// Accélérer // Accélérer
accelerateButton.addActionListener(e -> { accelerateButton.addActionListener(e -> {
String msg = car.accelerate(); String msg = car.accelerate();
messageLabel.setText(msg); messageLabel.setText(msg);
game.notifyObservers(); game.notifyObservers();
}); });
// Ralentir // Ralentir
decelerateButton.addActionListener(e -> { decelerateButton.addActionListener(e -> {
String msg = car.decelerate(); String msg = car.decelerate();
messageLabel.setText(msg); messageLabel.setText(msg);
game.notifyObservers(); game.notifyObservers();
}); });
} }
public void setCar(Car car) {
this.car = car;
}
/** /**
* Mise à jour de la vue. * Mise à jour de la vue.
* Cette méthode est appelée par Game via notifyObservers(). * Cette méthode est appelée par Game via notifyObservers().
*/ */
@Override @Override
public boolean apply() { public boolean apply() {
if (this.car == null) {
System.out.println("ERREUR Dashboard: car est null");
return false;
}
if (!super.apply()) {
System.out.println("ERREUR Dashboard: game est null");
return false;
}
infoLabel.setText("Carburant : " + car.getFuel() + " | Tours : " + car.getRound()); infoLabel.setText("Carburant : " + car.getFuel() + " | Tours : " + car.getRound());
stateLabel.setText("Etat : " + car.getState()); stateLabel.setText("Etat : " + car.getState());

View File

@@ -15,8 +15,11 @@ import model.Game;
*/ */
public abstract class GameView extends JComponent implements Game.Observer { public abstract class GameView extends JComponent implements Game.Observer {
/** Fenêtre associée à cette vue */ /** Fenêtre associée à cette vue */
protected final JFrame frame; protected JFrame frame;
protected final Game game; protected Game game;
private String title;
private int width, height;
private int x, y;
/** /**
* Bloc statique exécuté au chargement de la classe pour vérifier * Bloc statique exécuté au chargement de la classe pour vérifier
@@ -29,6 +32,10 @@ public abstract class GameView extends JComponent implements Game.Observer {
} }
} }
public void setGame(Game game) {
this.game = game;
}
/** /**
* Construit une nouvelle GameView avec une fenêtre JFrame. * Construit une nouvelle GameView avec une fenêtre JFrame.
* *
@@ -38,7 +45,7 @@ public abstract class GameView extends JComponent implements Game.Observer {
* @param x Position horizontale de la fenêtre à l'écran * @param x Position horizontale de la fenêtre à l'écran
* @param y Position verticale de la fenêtre à l'écran * @param y Position verticale de la fenêtre à l'écran
*/ */
protected GameView(Game game, String title, int width, int height, int x, int y) { protected void init(Game game) {
this.game = game; this.game = game;
// la fenetre // la fenetre
@@ -60,4 +67,20 @@ public abstract class GameView extends JComponent implements Game.Observer {
game.addObserver(this); game.addObserver(this);
} }
protected GameView(Game game, String title, int width, int height, int x, int y) {
this.title = title;
this.width = width;
this.height = height;
this.x = x;
this.y = y;
if (game != null)
init(game);
}
@Override
public boolean apply() {
return (game != null);
}
} }

View File

@@ -3,11 +3,12 @@ package visual;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List;
import javax.swing.JLabel; import javax.swing.JLabel;
import model.Game; import model.Game;
import model.car.BasicCar; import model.car.Car;
/** /**
* Rankboard est une vue graphique affichant le classement des voitures. * Rankboard est une vue graphique affichant le classement des voitures.
@@ -18,58 +19,68 @@ import model.car.BasicCar;
* </p> * </p>
*/ */
public class Rankboard extends GameView { public class Rankboard extends GameView {
/** Liste des voitures à afficher */ /** Liste des voitures à afficher */
ArrayList<BasicCar> cars; List<Car> cars;
/** Composant JLabel pour afficher le classement */ /** Composant JLabel pour afficher le classement */
private final JLabel label; private JLabel label;
/** @Override
* Construit un Rankboard. protected void init(Game game) {
* super.init(game);
* @param title Titre de la fenêtre this.cars = game.getCars();
* @param cars Liste des voitures à suivre this.label = new JLabel();
* @param width Largeur de la fenêtre this.add(label, BorderLayout.CENTER);
* @param height Hauteur de la fenêtre }
* @param x Position horizontale de la fenêtre
* @param y Position verticale de la fenêtre
*/
public Rankboard(Game game, String title, int width, int height, int x, int y) {
super(game, title, width, height, x, y);
this.cars = game.getCars();
this.label = new JLabel();
this.add(label, BorderLayout.CENTER);
}
/** /**
* Met à jour le texte affiché dans le JLabel. * Construit un Rankboard.
* <p> *
* Trie les voitures par score décroissant et construit * @param title Titre de la fenêtre
* un tableau HTML pour l'affichage. * @param cars Liste des voitures à suivre
* </p> * @param width Largeur de la fenêtre
*/ * @param height Hauteur de la fenêtre
private void updateRankText() { * @param x Position horizontale de la fenêtre
* @param y Position verticale de la fenêtre
*/
public Rankboard(Game game, String title, int width, int height, int x, int y) {
super(game, title, width, height, x, y);
if (game != null)
init(game);
}
// cloner pour de modifier la classe principale /**
ArrayList<BasicCar> cars_clone = new ArrayList<>(cars); * Met à jour le texte affiché dans le JLabel.
cars_clone.sort(Comparator.comparingInt(BasicCar::getScore).reversed()); * <p>
* Trie les voitures par score décroissant et construit
* un tableau HTML pour l'affichage.
* </p>
*/
private void updateRankText() {
// cloner pour de modifier la classe principale
List<Car> cars_clone = new ArrayList<>(cars);
cars_clone.sort(Comparator.comparingInt(Car::getScore).reversed());
StringBuilder s = new StringBuilder(); StringBuilder s = new StringBuilder();
s.append("<html><table>"); s.append("<html><table>");
for (BasicCar c : cars_clone) { for (Car c : cars_clone) {
s.append("<tr><td>" + c.getName() + ": " + c.getScore() + "%</td></tr>"); s.append("<tr><td>" + c.getName() + ": " + c.getScore() + "%</td></tr>");
} }
s.append("</table></html>"); s.append("</table></html>");
label.setText(s.toString()); label.setText(s.toString());
} }
@Override @Override
/** /**
* Méthode appelée par GameView.update(). * Méthode appelée par GameView.update().
* Elle met à jour le classement affiché. * Elle met à jour le classement affiché.
*/ */
public boolean apply() { public boolean apply() {
updateRankText(); if (!super.apply()) {
return true; System.out.println("ERREUR Rankboard: game est null");
} return false;
}
updateRankText();
return true;
}
} }

View File

@@ -1,216 +1,229 @@
package visual; package visual;
import java.awt.Color; import java.awt.Color;
import java.awt.Font; import java.awt.Font;
import java.awt.FontMetrics; import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Point; import java.awt.Point;
import java.util.ArrayList; import java.util.List;
import model.Game; import model.Game;
import model.car.BasicCar; import model.car.Car;
import model.map.Circuit; import model.map.Circuit;
import model.map.Map; import model.map.Map;
/** /**
* <code>Track</code> est une vue graphique représentant le circuit de course * <code>Track</code> est une vue graphique représentant le circuit de course
* ainsi que les voitures qui y circulent. * ainsi que les voitures qui y circulent.
* Cette classe hérite de <code>GameView</code> pour être intégrée au système de vues. * Cette classe hérite de <code>GameView</code> pour être intégrée au système de
* vues.
* <p> * <p>
* Le rendu est basé sur une grille définie par <code>Map</code> et chaque cellule * Le rendu est basé sur une grille définie par <code>Map</code> et chaque
* cellule
* est dessinée selon son type. Les voitures sont superposées sur la grille. * est dessinée selon son type. Les voitures sont superposées sur la grille.
* </p> * </p>
*/ */
public class Track extends GameView { public class Track extends GameView {
/** La carte du circuit */ /** La carte du circuit */
private Map map; private Map map;
/** Liste des voitures à dessiner */ /** Liste des voitures à dessiner */
private ArrayList<BasicCar> cars; private List<Car> cars;
/** Échelle utilisée pour ajuster la taille des voitures dans les cellules */ /** Échelle utilisée pour ajuster la taille des voitures dans les cellules */
private final int scale = 80; private final int scale = 80;
/** @Override
* Construit la vue Track avec une carte et une liste de voitures. protected void init(Game game) {
* super.init(game);
* @param map La carte du circuit map = game.getMap();
* @param cars Liste des voitures cars = game.getCars();
* @param title Titre de la fenêtre }
* @param width Largeur de la fenêtre
* @param height Hauteur de la fenêtre
* @param x Position X de la fenêtre
* @param y Position Y de la fenêtre
*/
protected Track(Game game, String title, int width, int height, int x, int y)
{
super(game, title, width, height, x, y);
map = game.getMap();
cars = game.getCars();
}
/** /**
* Méthode de dessin appelée automatiquement par Swing. * Construit la vue Track avec une carte et une liste de voitures.
* Dessine chaque cellule de la carte ainsi que les voitures. *
*/ * @param map La carte du circuit
@Override * @param cars Liste des voitures
protected void paintComponent(Graphics g) { * @param title Titre de la fenêtre
super.paintComponent(g); * @param width Largeur de la fenêtre
* @param height Hauteur de la fenêtre
* @param x Position X de la fenêtre
* @param y Position Y de la fenêtre
*/
public Track(Game game, String title, int width, int height, int x, int y) {
super(game, title, width, height, x, y);
if (game != null)
init(game);
}
int rows = map.getHeight(); /**
int cols = map.getWidth(); * Méthode de dessin appelée automatiquement par Swing.
* Dessine chaque cellule de la carte ainsi que les voitures.
*/
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Calcul de la taille d'une cellule en pixels selon la taille de la fenêtre int rows = map.getHeight();
int[] cellSize = new int[] { int cols = map.getWidth();
(getWidth() + 10) / cols,
(getHeight() + 10) / rows
};
// Définir une police monospace en gras pour les caractères du circuit // Calcul de la taille d'une cellule en pixels selon la taille de la fenêtre
g.setFont(new Font("Monospaced", Font.BOLD, cellSize[1] / 2)); int[] cellSize = new int[] {
(getWidth() + 10) / cols,
(getHeight() + 10) / rows
};
// Parcours de toutes les cellules du circuit // Définir une police monospace en gras pour les caractères du circuit
for (int y = 0; y < rows; y++) { g.setFont(new Font("Monospaced", Font.BOLD, cellSize[1] / 2));
for (int x = 0; x < cols; x++) {
Circuit cell = this.map.getElement(x, y);
drawCell(g, cell, new int[] {x, y}, cellSize);
}
}
}
/** // Parcours de toutes les cellules du circuit
* Retourne la couleur d'une cellule selon son type. for (int y = 0; y < rows; y++) {
*/ for (int x = 0; x < cols; x++) {
private Color getCellColor(Circuit.Cell cell) { Circuit cell = this.map.getElement(x, y);
return switch (cell) { drawCell(g, cell, new int[] { x, y }, cellSize);
case ROAD -> Color.GRAY; }
case START -> Color.YELLOW; }
case FINISH -> Color.YELLOW; }
case EMPTY -> Color.GREEN;
case YROAD -> Color.YELLOW;
};
}
/** /**
* Retourne le caractère à afficher pour une cellule du circuit. * Retourne la couleur d'une cellule selon son type.
*/ */
private char getCellChar(Circuit cell) { private Color getCellColor(Circuit.Cell cell) {
return switch (cell.getType()) { return switch (cell) {
case ROAD -> '\0'; case ROAD -> Color.GRAY;
case START -> 'D'; case START -> Color.YELLOW;
case FINISH -> 'A'; case FINISH -> Color.YELLOW;
case EMPTY -> '\0'; case EMPTY -> Color.GREEN;
case YROAD -> (char)('0' + cell.getValue()); case YROAD -> Color.YELLOW;
}; };
} }
/** /**
* Dessine toutes les voitures sur une cellule donnée. * Retourne le caractère à afficher pour une cellule du circuit.
* Les voitures sont légèrement décalées pour ne pas avoir exactement la même taille. */
*/ private char getCellChar(Circuit cell) {
private void drawCars(Graphics g, int[] cellCoord) { return switch (cell.getType()) {
int size = 1; case ROAD -> '\0';
for (BasicCar c : cars) { case START -> 'D';
int i = c.getPos(); case FINISH -> 'A';
Point p = this.map.getPath(i); case EMPTY -> '\0';
int[] ncoord = new int[] {p.x, p.y}; case YROAD -> (char) ('0' + cell.getValue());
drawInnerBlock(g, ncoord, cellCoord, c.getColor(), scale - size); };
size += 3; // pour différencier visuellement les voitures }
}
}
/** /**
* Dessine une cellule complète avec son contenu (caractère et voitures). * Dessine toutes les voitures sur une cellule donnée.
*/ * Les voitures sont légèrement décalées pour ne pas avoir exactement la même
private void drawCell(Graphics g, Circuit cell, int[] coord, int[] cellCoord) { * taille.
drawBlock(g, cell, coord, cellCoord); */
private void drawCars(Graphics g, int[] cellCoord) {
int size = 1;
for (Car c : cars) {
int i = c.getPosition();
Point p = this.map.getPath(i);
int[] ncoord = new int[] { p.x, p.y };
drawInnerBlock(g, ncoord, cellCoord, c.getColor(), scale - size);
size += 3; // pour différencier visuellement les voitures
}
}
char c = getCellChar(cell); /**
if (c != '\0') { * Dessine une cellule complète avec son contenu (caractère et voitures).
drawCharacter(g, coord, cellCoord, c); */
} private void drawCell(Graphics g, Circuit cell, int[] coord, int[] cellCoord) {
drawBlock(g, cell, coord, cellCoord);
drawCars(g, cellCoord); char c = getCellChar(cell);
} if (c != '\0') {
drawCharacter(g, coord, cellCoord, c);
}
/** drawCars(g, cellCoord);
* Dessine un caractère centré dans une cellule. }
*/
private void drawCharacter(Graphics g, int[] coord, int[] cellCoord, char c) {
int x = coord[0];
int y = coord[1];
int cellWidth = cellCoord[0]; /**
int cellHeight = cellCoord[1]; * Dessine un caractère centré dans une cellule.
*/
private void drawCharacter(Graphics g, int[] coord, int[] cellCoord, char c) {
int x = coord[0];
int y = coord[1];
String s = c + ""; int cellWidth = cellCoord[0];
g.setColor(Color.BLACK); int cellHeight = cellCoord[1];
FontMetrics fm = g.getFontMetrics(); String s = c + "";
// taille du string + font g.setColor(Color.BLACK);
int textWidth = fm.stringWidth(s);
// hauteur haute par exemple: h depasse en haut
int textHeight = fm.getAscent();
// hauteur basse par exemple: g depasse en bas
int descent = fm.getDescent();
int cx = x * cellWidth + (cellWidth - textWidth) / 2; FontMetrics fm = g.getFontMetrics();
int cy = y * cellHeight + (cellHeight + textHeight - descent) / 2; // taille du string + font
int textWidth = fm.stringWidth(s);
// hauteur haute par exemple: h depasse en haut
int textHeight = fm.getAscent();
// hauteur basse par exemple: g depasse en bas
int descent = fm.getDescent();
g.drawString(s, cx, cy); int cx = x * cellWidth + (cellWidth - textWidth) / 2;
} int cy = y * cellHeight + (cellHeight + textHeight - descent) / 2;
/** g.drawString(s, cx, cy);
* Dessine un bloc intérieur plus petit, utilisé pour représenter une voiture }
*/
private void drawInnerBlock(Graphics g, int[] coord, int[] cellCoord, Color c, int scale) {
// scale de la taille du bloc
int w = cellCoord[0] * scale / 100;
int h = cellCoord[1] * scale / 100;
// calcul de la position avec la nouvelle taille /**
int ox = coord[0] * cellCoord[0] + (cellCoord[0] - w) / 2; * Dessine un bloc intérieur plus petit, utilisé pour représenter une voiture
int oy = coord[1] * cellCoord[1] + (cellCoord[1] - h) / 2; */
private void drawInnerBlock(Graphics g, int[] coord, int[] cellCoord, Color c, int scale) {
// scale de la taille du bloc
int w = cellCoord[0] * scale / 100;
int h = cellCoord[1] * scale / 100;
// dessine le bloc plus petit // calcul de la position avec la nouvelle taille
g.setColor(c); int ox = coord[0] * cellCoord[0] + (cellCoord[0] - w) / 2;
g.fillRect(ox, oy, w, h); int oy = coord[1] * cellCoord[1] + (cellCoord[1] - h) / 2;
}
/** // dessine le bloc plus petit
* Dessine une cellule de base selon son type et ajoute un contour noir. g.setColor(c);
*/ g.fillRect(ox, oy, w, h);
private void drawBlock(Graphics g, Circuit cell, int[] coord, int[] cellCoord) { }
int x = coord[0];
int y = coord[1];
int cellWidth = cellCoord[0]; /**
int cellHeight = cellCoord[1]; * Dessine une cellule de base selon son type et ajoute un contour noir.
*/
private void drawBlock(Graphics g, Circuit cell, int[] coord, int[] cellCoord) {
int x = coord[0];
int y = coord[1];
switch (cell.getType()) { int cellWidth = cellCoord[0];
case YROAD: int cellHeight = cellCoord[1];
// dessine le bloc de route
g.setColor(getCellColor(Circuit.Cell.ROAD));
g.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
// dessine le sous bloc de route
drawInnerBlock(g, coord, cellCoord, getCellColor(cell.getType()), scale);
break;
default:
g.setColor(getCellColor(cell.getType()));
g.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
break;
}
// contour noir switch (cell.getType()) {
g.setColor(Color.BLACK); case YROAD:
g.drawRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight); // dessine le bloc de route
} g.setColor(getCellColor(Circuit.Cell.ROAD));
g.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
/** // dessine le sous bloc de route
* Méthode appelée automatiquement pour mettre à jour l'affichage. drawInnerBlock(g, coord, cellCoord, getCellColor(cell.getType()), scale);
*/ break;
@Override default:
public boolean apply() g.setColor(getCellColor(cell.getType()));
{ g.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
repaint(); break;
return true; }
}
// contour noir
g.setColor(Color.BLACK);
g.drawRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
}
/**
* Méthode appelée automatiquement pour mettre à jour l'affichage.
*/
@Override
public boolean apply() {
if (!super.apply()) {
System.out.println("ERREUR Track: game est null");
return false;
}
repaint();
return true;
}
} }