diff --git a/src/Main.java b/src/Main.java index 16c3a43..b4c05c6 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,12 +1,34 @@ +import java.awt.Color; import java.util.List; import model.Game; +import model.car.BasicCar; +import model.car.DrunkCar; import model.map.Map; public class Main { public static void main(String[] args) throws InterruptedException { - Map map = Game.GameFactory.defaultMap(); - Game game = Game.GameFactory.defaultGame(map); + Map map = 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 }, + }); + + 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(); } } \ No newline at end of file diff --git a/src/model/Game.java b/src/model/Game.java index 3fafb5f..a0e109f 100644 --- a/src/model/Game.java +++ b/src/model/Game.java @@ -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. - *

- * 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). - *

- */ 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). - *

- * Chaque instance contient toutes les informations nécessaires pour créer et - * positionner - * cette vue à l’écran. - *

- * - * @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 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. - *

- * 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. - *

- */ public static class Builder { - /** Liste des voitures à créer pour le jeu */ - private ArrayList cars = new ArrayList<>(); - /** Liste des voitures à créer pour le jeu */ - private ArrayList 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 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 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 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. - *

- * Si la carte n'a pas été définie, le programme s'arrête. - *

- */ 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 observers; - /** Liste des voitures du jeu */ - private final ArrayList cars = new ArrayList<>(); - /** Liste des observateurs pour la mise à jour des vues */ - private final ArrayList 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 carInfos, Car.State state, int time) { + private Game(Map map, int time, List 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}. - *

- * Chaque voiture obtient un Dashboard, la piste est affichée avec Track, - * et le Rankboard est créé pour afficher le classement. - *

- * - * @param carInfos liste des informations des voitures - * @return l'instance de Game - */ - private Game init(ArrayList 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é. - *

- * Le jeu est fini si au moins une voiture a épuisé son carburant. - *

- * - * @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. - *

- * Si le jeu était en pause, il est relancé et les threads en attente sont - * notifiés. - *

- * - * @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 - *

- * Chaque voiture effectue son action, puis les observateurs sont notifiés. - * La boucle attend si le jeu est en pause. - *

- * - * @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. - *

- * Tant que le jeu n'est pas terminé, on exécute les étapes et on met à jour - * les vues toutes les 'time' millisecondes. - *

- */ - 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 getCars() { - return cars; + public List 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; } diff --git a/src/model/car/BasicCar.java b/src/model/car/BasicCar.java index 790ec1a..3369353 100644 --- a/src/model/car/BasicCar.java +++ b/src/model/car/BasicCar.java @@ -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 { *

*/ @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; + } } diff --git a/src/model/car/Car.java b/src/model/car/Car.java index 1ce5787..2884300 100644 --- a/src/model/car/Car.java +++ b/src/model/car/Car.java @@ -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); } diff --git a/src/model/car/CarDecorator.java b/src/model/car/CarDecorator.java new file mode 100644 index 0000000..a6a3ae9 --- /dev/null +++ b/src/model/car/CarDecorator.java @@ -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); + } +} \ No newline at end of file diff --git a/src/model/car/DrunkCar.java b/src/model/car/DrunkCar.java index 6b699e6..8871a57 100644 --- a/src/model/car/DrunkCar.java +++ b/src/model/car/DrunkCar.java @@ -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(); - } } \ No newline at end of file diff --git a/src/model/car/HybridCar.java b/src/model/car/HybridCar.java index c1c88da..db49a20 100644 --- a/src/model/car/HybridCar.java +++ b/src/model/car/HybridCar.java @@ -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(); - } } diff --git a/src/model/car/SoundCar.java b/src/model/car/SoundCar.java index 8353fad..234ee1c 100644 --- a/src/model/car/SoundCar.java +++ b/src/model/car/SoundCar.java @@ -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'"); - } } diff --git a/src/visual/Dashboard.java b/src/visual/Dashboard.java index 74c2c79..970a8f3 100644 --- a/src/visual/Dashboard.java +++ b/src/visual/Dashboard.java @@ -7,21 +7,21 @@ import javax.swing.JLabel; import javax.swing.JPanel; import model.Game; -import model.car.BasicCar; +import model.car.Car; /** * Dashboard représente une vue graphique pour une voiture spécifique. * * Il affiche : - * - le carburant restant - * - le nombre de tours complétés - * - l'état courant de la voiture - * - un message (info utilisateur) + * - le carburant restant + * - le nombre de tours complétés + * - l'état courant de la voiture + * - un message (info utilisateur) * * Il fournit : - * - un bouton Pause/Reprise du jeu - * - un bouton Accelerer - * - un bouton Rallentir + * - un bouton Pause/Reprise du jeu + * - un bouton Accelerer + * - un bouton Rallentir */ public class Dashboard extends GameView { @@ -44,7 +44,7 @@ public class Dashboard extends GameView { private final JButton decelerateButton = new JButton("Rallentir"); /** Voiture associée à ce dashboard */ - private final BasicCar car; + private Car car; /** * 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 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); - this.car = car; - - + if (car != null && game != null) + init(); + } + + private void init() { // Fond de la fenêtre = couleur de la voiture frame.setBackground(car.getColor()); @@ -72,7 +74,7 @@ public class Dashboard extends GameView { centerPanel.add(infoLabel); centerPanel.add(stateLabel); centerPanel.add(messageLabel); - //pour rendre le panel transparent + // pour rendre le panel transparent centerPanel.setOpaque(false); // ----- Partie boutons ----- @@ -80,7 +82,6 @@ public class Dashboard extends GameView { buttonPanel.add(decelerateButton); buttonPanel.add(pauseButton); buttonPanel.add(accelerateButton); - setLayout(new BorderLayout()); add(centerPanel, BorderLayout.CENTER); @@ -108,25 +109,38 @@ public class Dashboard extends GameView { // Accélérer accelerateButton.addActionListener(e -> { - String msg = car.accelerate(); + String msg = car.accelerate(); messageLabel.setText(msg); game.notifyObservers(); }); // Ralentir decelerateButton.addActionListener(e -> { - String msg = car.decelerate(); + String msg = car.decelerate(); messageLabel.setText(msg); game.notifyObservers(); }); } + public void setCar(Car car) { + this.car = car; + } + /** * Mise à jour de la vue. * Cette méthode est appelée par Game via notifyObservers(). */ @Override 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()); stateLabel.setText("Etat : " + car.getState()); diff --git a/src/visual/GameView.java b/src/visual/GameView.java index 6b1f502..b40fdf7 100644 --- a/src/visual/GameView.java +++ b/src/visual/GameView.java @@ -15,8 +15,11 @@ import model.Game; */ public abstract class GameView extends JComponent implements Game.Observer { /** Fenêtre associée à cette vue */ - protected final JFrame frame; - protected final Game game; + protected JFrame frame; + 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 @@ -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. * @@ -38,7 +45,7 @@ public abstract class GameView extends JComponent implements Game.Observer { * @param x Position horizontale 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; // la fenetre @@ -60,4 +67,20 @@ public abstract class GameView extends JComponent implements Game.Observer { 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); + } } \ No newline at end of file diff --git a/src/visual/Rankboard.java b/src/visual/Rankboard.java index 69a8eba..6f47cc5 100644 --- a/src/visual/Rankboard.java +++ b/src/visual/Rankboard.java @@ -3,11 +3,12 @@ package visual; import java.awt.BorderLayout; import java.util.ArrayList; import java.util.Comparator; +import java.util.List; import javax.swing.JLabel; import model.Game; -import model.car.BasicCar; +import model.car.Car; /** * Rankboard est une vue graphique affichant le classement des voitures. @@ -18,58 +19,68 @@ import model.car.BasicCar; *

*/ public class Rankboard extends GameView { - /** Liste des voitures à afficher */ - ArrayList cars; + /** Liste des voitures à afficher */ + List cars; - /** Composant JLabel pour afficher le classement */ - private final JLabel label; + /** Composant JLabel pour afficher le classement */ + private JLabel label; - /** - * Construit un Rankboard. - * - * @param title Titre de la fenêtre - * @param cars Liste des voitures à suivre - * @param width Largeur de la fenêtre - * @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); - } + @Override + protected void init(Game game) { + super.init(game); + this.cars = game.getCars(); + this.label = new JLabel(); + this.add(label, BorderLayout.CENTER); + } - /** - * Met à jour le texte affiché dans le JLabel. - *

- * Trie les voitures par score décroissant et construit - * un tableau HTML pour l'affichage. - *

- */ - private void updateRankText() { + /** + * Construit un Rankboard. + * + * @param title Titre de la fenêtre + * @param cars Liste des voitures à suivre + * @param width Largeur de la fenêtre + * @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); + if (game != null) + init(game); + } - // cloner pour de modifier la classe principale - ArrayList cars_clone = new ArrayList<>(cars); - cars_clone.sort(Comparator.comparingInt(BasicCar::getScore).reversed()); + /** + * Met à jour le texte affiché dans le JLabel. + *

+ * Trie les voitures par score décroissant et construit + * un tableau HTML pour l'affichage. + *

+ */ + private void updateRankText() { + // cloner pour de modifier la classe principale + List cars_clone = new ArrayList<>(cars); + cars_clone.sort(Comparator.comparingInt(Car::getScore).reversed()); - StringBuilder s = new StringBuilder(); - s.append(""); - for (BasicCar c : cars_clone) { - s.append(""); - } - s.append("
" + c.getName() + ": " + c.getScore() + "%
"); - label.setText(s.toString()); - } + StringBuilder s = new StringBuilder(); + s.append(""); + for (Car c : cars_clone) { + s.append(""); + } + s.append("
" + c.getName() + ": " + c.getScore() + "%
"); + label.setText(s.toString()); + } - @Override - /** - * Méthode appelée par GameView.update(). - * Elle met à jour le classement affiché. - */ - public boolean apply() { - updateRankText(); - return true; - } + @Override + /** + * Méthode appelée par GameView.update(). + * Elle met à jour le classement affiché. + */ + public boolean apply() { + if (!super.apply()) { + System.out.println("ERREUR Rankboard: game est null"); + return false; + } + updateRankText(); + return true; + } } diff --git a/src/visual/Track.java b/src/visual/Track.java index 66dc6c8..f4d43ff 100644 --- a/src/visual/Track.java +++ b/src/visual/Track.java @@ -1,216 +1,229 @@ package visual; + import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Point; -import java.util.ArrayList; +import java.util.List; import model.Game; -import model.car.BasicCar; +import model.car.Car; import model.map.Circuit; import model.map.Map; /** * Track est une vue graphique représentant le circuit de course - * ainsi que les voitures qui y circulent. - * Cette classe hérite de GameView pour être intégrée au système de vues. + * ainsi que les voitures qui y circulent. + * Cette classe hérite de GameView pour être intégrée au système de + * vues. *

- * Le rendu est basé sur une grille définie par Map et chaque cellule + * Le rendu est basé sur une grille définie par Map et chaque + * cellule * est dessinée selon son type. Les voitures sont superposées sur la grille. *

*/ public class Track extends GameView { - /** La carte du circuit */ - private Map map; - /** Liste des voitures à dessiner */ - private ArrayList cars; - /** Échelle utilisée pour ajuster la taille des voitures dans les cellules */ - private final int scale = 80; + /** La carte du circuit */ + private Map map; + /** Liste des voitures à dessiner */ + private List cars; + /** Échelle utilisée pour ajuster la taille des voitures dans les cellules */ + private final int scale = 80; - /** - * Construit la vue Track avec une carte et une liste de voitures. - * - * @param map La carte du circuit - * @param cars Liste des voitures - * @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(); - } + @Override + protected void init(Game game) { + super.init(game); + map = game.getMap(); + cars = game.getCars(); + } - /** - * 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); + /** + * Construit la vue Track avec une carte et une liste de voitures. + * + * @param map La carte du circuit + * @param cars Liste des voitures + * @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 + */ + 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[] cellSize = new int[] { - (getWidth() + 10) / cols, - (getHeight() + 10) / rows - }; + int rows = map.getHeight(); + int cols = map.getWidth(); - // Définir une police monospace en gras pour les caractères du circuit - g.setFont(new Font("Monospaced", Font.BOLD, cellSize[1] / 2)); + // Calcul de la taille d'une cellule en pixels selon la taille de la fenêtre + int[] cellSize = new int[] { + (getWidth() + 10) / cols, + (getHeight() + 10) / rows + }; - // Parcours de toutes les cellules du circuit - for (int y = 0; y < rows; y++) { - for (int x = 0; x < cols; x++) { - Circuit cell = this.map.getElement(x, y); - drawCell(g, cell, new int[] {x, y}, cellSize); - } - } - } + // Définir une police monospace en gras pour les caractères du circuit + g.setFont(new Font("Monospaced", Font.BOLD, cellSize[1] / 2)); - /** - * Retourne la couleur d'une cellule selon son type. - */ - private Color getCellColor(Circuit.Cell cell) { - return switch (cell) { - case ROAD -> Color.GRAY; - case START -> Color.YELLOW; - case FINISH -> Color.YELLOW; - case EMPTY -> Color.GREEN; - case YROAD -> Color.YELLOW; - }; - } + // Parcours de toutes les cellules du circuit + for (int y = 0; y < rows; y++) { + for (int x = 0; x < cols; x++) { + Circuit cell = this.map.getElement(x, y); + drawCell(g, cell, new int[] { x, y }, cellSize); + } + } + } - /** - * Retourne le caractère à afficher pour une cellule du circuit. - */ - private char getCellChar(Circuit cell) { - return switch (cell.getType()) { - case ROAD -> '\0'; - case START -> 'D'; - case FINISH -> 'A'; - case EMPTY -> '\0'; - case YROAD -> (char)('0' + cell.getValue()); - }; - } + /** + * Retourne la couleur d'une cellule selon son type. + */ + private Color getCellColor(Circuit.Cell cell) { + return switch (cell) { + case ROAD -> Color.GRAY; + case START -> Color.YELLOW; + case FINISH -> Color.YELLOW; + case EMPTY -> Color.GREEN; + case YROAD -> Color.YELLOW; + }; + } - /** - * 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 taille. - */ - private void drawCars(Graphics g, int[] cellCoord) { - int size = 1; - for (BasicCar c : cars) { - int i = c.getPos(); - 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 - } - } + /** + * Retourne le caractère à afficher pour une cellule du circuit. + */ + private char getCellChar(Circuit cell) { + return switch (cell.getType()) { + case ROAD -> '\0'; + case START -> 'D'; + case FINISH -> 'A'; + case EMPTY -> '\0'; + case YROAD -> (char) ('0' + cell.getValue()); + }; + } - /** - * Dessine une cellule complète avec son contenu (caractère et voitures). - */ - private void drawCell(Graphics g, Circuit cell, int[] coord, int[] cellCoord) { - drawBlock(g, cell, coord, cellCoord); + /** + * 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 + * taille. + */ + 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') { - drawCharacter(g, coord, cellCoord, c); - } + /** + * Dessine une cellule complète avec son contenu (caractère et voitures). + */ + 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); + } - /** - * 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]; + drawCars(g, cellCoord); + } - 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 + ""; - g.setColor(Color.BLACK); + int cellWidth = cellCoord[0]; + int cellHeight = cellCoord[1]; - FontMetrics fm = g.getFontMetrics(); - // 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(); + String s = c + ""; + g.setColor(Color.BLACK); - int cx = x * cellWidth + (cellWidth - textWidth) / 2; - int cy = y * cellHeight + (cellHeight + textHeight - descent) / 2; + FontMetrics fm = g.getFontMetrics(); + // 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; - /** - * 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; + g.drawString(s, cx, cy); + } - // calcul de la position avec la nouvelle taille - int ox = coord[0] * cellCoord[0] + (cellCoord[0] - w) / 2; - int oy = coord[1] * cellCoord[1] + (cellCoord[1] - h) / 2; + /** + * 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; - // dessine le bloc plus petit - g.setColor(c); - g.fillRect(ox, oy, w, h); - } + // calcul de la position avec la nouvelle taille + int ox = coord[0] * cellCoord[0] + (cellCoord[0] - w) / 2; + int oy = coord[1] * cellCoord[1] + (cellCoord[1] - h) / 2; - /** - * 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]; + // dessine le bloc plus petit + g.setColor(c); + g.fillRect(ox, oy, w, h); + } - 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()) { - case YROAD: - // 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; - } + int cellWidth = cellCoord[0]; + int cellHeight = cellCoord[1]; - // contour noir - g.setColor(Color.BLACK); - g.drawRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight); - } + switch (cell.getType()) { + case YROAD: + // dessine le bloc de route + g.setColor(getCellColor(Circuit.Cell.ROAD)); + g.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight); - /** - * Méthode appelée automatiquement pour mettre à jour l'affichage. - */ - @Override - public boolean apply() - { - repaint(); - return true; - } + // 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 + 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; + } }