From 748ae70207bacdb7fb058ea2d345734ad85742a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20GUEZO?= Date: Thu, 18 Dec 2025 16:17:10 +0100 Subject: [PATCH] feat!: rework de tout le projet --- src/Car.java | 456 -------------------------------- src/Main.java | 3 + src/Rankboard.java | 75 ------ src/{ => model}/Game.java | 19 +- src/model/car/BasicCar.java | 247 +++++++++++++++++ src/model/car/Car.java | 23 ++ src/model/car/DrunkCar.java | 75 ++++++ src/model/car/HybridCar.java | 91 +++++++ src/model/car/SoundCar.java | 62 +++++ src/model/car/State.java | 146 ++++++++++ src/model/map/Circuit.java | 136 ++++++++++ src/{ => model/map}/Map.java | 136 +--------- src/{ => visual}/Dashboard.java | 9 +- src/{ => visual}/GameView.java | 53 ++-- src/visual/Rankboard.java | 75 ++++++ src/{ => visual}/Track.java | 22 +- 16 files changed, 916 insertions(+), 712 deletions(-) delete mode 100644 src/Car.java delete mode 100644 src/Rankboard.java rename src/{ => model}/Game.java (96%) create mode 100644 src/model/car/BasicCar.java create mode 100644 src/model/car/Car.java create mode 100644 src/model/car/DrunkCar.java create mode 100644 src/model/car/HybridCar.java create mode 100644 src/model/car/SoundCar.java create mode 100644 src/model/car/State.java create mode 100644 src/model/map/Circuit.java rename src/{ => model/map}/Map.java (70%) rename src/{ => visual}/Dashboard.java (95%) rename src/{ => visual}/GameView.java (52%) create mode 100644 src/visual/Rankboard.java rename src/{ => visual}/Track.java (91%) diff --git a/src/Car.java b/src/Car.java deleted file mode 100644 index 260c82a..0000000 --- a/src/Car.java +++ /dev/null @@ -1,456 +0,0 @@ -import java.awt.Color; -import java.awt.Point; -import java.util.List; -import java.util.Random; - -/** - * {@link Car} représente une voiture qui avance sur un circuit en boucles. - * Chaque appel à {@link #run()} avance la voiture d'une certaine position et - * fait perdre de l'essence. - * Quand la position atteint la fin de la boucle, un nouveau tour est compté. - *

- * La voiture consomme du carburant à chaque déplacement, et son score - * est calculé en fonction du nombre de tours complétés et de sa progression sur - * le tour actuel. - *

- */ -public class Car { - /** Générateur de nombres aléatoires pour simuler la progression */ - protected static final Random RANDOM = new Random(); - - /** Couleur de la voiture pour l'affichage */ - private final Color COLOR; - /** Nom de la voiture */ - private final String NAME; - /** Position actuelle dans la boucle (entre 0 et loop) */ - private int pos = 0; - /** Nombre de tours complétés */ - private int round = 0; - /** Nombre de cases dans une boucle (doit être > 0) */ - private final Map MAP; - /** Carburant restant */ - private int fuel = 60; - - private boolean isReversing = false; - - /** - * Construit une nouvelle voiture. - * - * @param name nom de la voiture - * @param color couleur de la voiture - * @param map - */ - public Car(String name, Color color, Map map) { - this.NAME = name; - this.COLOR = color; - this.MAP = map; - } - - public Car(Car other) { - this.NAME = other.NAME; - this.COLOR = other.COLOR; - this.MAP = other.MAP; - } - - public static enum State { - /** - * L'état NORMAL du Vehicule avance selon un chiffre au alentour de 1 à 6 cases - * par tour. Il consomme 2 unités de carburant à chaque tour. Si l'on - * accelere, il passe à l'état BOOST. Si on Rallenti, il passe à l'état LOW. - */ - // @formatter:off - NORMAL(2, 1, 6) { - public State accelerate() { return BOOST; } - public State decelerate() { return LOW; } - }, - - /** - * L'état BOOST du Vehicule avance selon un chiffre au alentour de 5 à 10 cases - * par tour. Il consomme 5 unités de carburant à chaque tour. Si l'on - * accelere, il reste sur son état et indique un message sur le tableau de bord. - * Si on Rallenti, il passe à l'état LOW. - */ - BOOST(5, 5, 10) { - public State accelerate() { return this; } - public State decelerate() { return NORMAL; } - }, - - /** - * L'état LOW du Vehicule avance selon un chiffre au alentour de 1 à 3 cases - * par tour. Il consomme 1 unités de carburant à chaque tour. Si l'on - * accelere, il passe à l'état NORMAL. Si on Rallenti, il passe à l'état STOPPED. - */ - LOW(1, 1, 3) { - public State accelerate() { return NORMAL; } - public State decelerate() { return STOPPED; } - }, - - /** - * L'état STOPPED du Vehicule n'avance pas. Il consomme aucune unités de - * carburant à chaque tour. Si l'on - * accelere, il passe à l'état LOW. Si on Rallenti, il reste sur son état et - * indique un message sur le tableau de bord. - */ - STOPPED(0, 0, 0) { - public State accelerate() { return LOW; } - public State decelerate() { return this; } - @Override - public int move(int pos, int jump) { return pos; } - @Override - public int fuelConsumption(int fuel) { return fuel; } - }, - /** - * L'état STOPPED du Vehicule n'avance pas. Il consomme aucune unités de - * carburant à chaque tour. Il reste immobile. - */ - DAMAGED(0, 0, 0) { - public State accelerate() { return this; } - public State decelerate() { return this; } - - @Override - public int move(int pos, int jump) { return pos; } - @Override - public int fuelConsumption(int fuel) { return fuel; } - @Override - public boolean isDamaged() { return true; } - @Override - public State onDamageEnd() { return LOW; } - }; - // @formatter:on - - public final int FUELCOST; - public final int MAX; - public final int MIN; - - private State(int fuelCost, int min, int max) { - this.FUELCOST = fuelCost; - this.MAX = max + 1; - this.MIN = min; - } - - public List getInterval() { - return List.of(MIN, MAX); - } - - public int fuelConsumption(int fuel) { - return fuel - FUELCOST; - } - - public int move(int pos, int jump) { - return pos + jump; - } - - public boolean isDamaged() { - return false; - } - - public State onDamageEnd() { - return this; - } - - public abstract State accelerate(); - - public abstract State decelerate(); - } - - /** - * Référence à l'état du jeu, pour récupérer les paramètres comme la - * consommation - */ - private State state = State.NORMAL; - - private int damageRound = 0; - - public void setDamage() { - damageRound = 5; - state = State.DAMAGED; - } - - private boolean decreaseDamage() { - if (state.isDamaged()) { - if (--damageRound <= 0) { - state = state.onDamageEnd(); - } - return true; - } - return false; - } - - public Car setState(State state) { - this.state = state; - 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). - */ - public String accelerate() { - // Si endommagée => l'énoncé dit qu'on ne peut pas bouger - if (state.isDamaged()) { - return "Voiture endommagée : impossible d'accélérer"; - } - State next = state.accelerate(); - - // Énoncé : en BOOST, si on accélère encore => message "déjà max" - if (state == State.BOOST && next == State.BOOST) { - return "Déjà à la vitesse maximale"; - } - state = next; - return ""; - } - - /** - * Clique sur "Rallentir" : change d'état et retourne un message (si - * nécessaire). - */ - public String decelerate() { - if (state.isDamaged()) { - return "Voiture endommagée : impossible de ralentir"; - } - - State next = state.decelerate(); - - // Énoncé : en STOPPED, si on ralentit encore => message "déjà arrêtée" - if (state == State.STOPPED && next == State.STOPPED) { - return "Déjà arrêtée"; - } - - state = next; - return ""; - } - - /** - * Fait avancer la voiture d'un certain nombre de positions. - *

- * Si la position atteint la fin de la boucle, le compteur de tours est - * incrémenté et - * la position revient à zéro. - *

- * - * @param move nombre de positions à avancer - * @return cette même instance (pour chaînage fluide) - */ - public void move() { - if (decreaseDamage()) { - System.out.println(NAME + " est en\taccident " + damageRound); - return; - } - - int jump = RANDOM.nextInt(state.MIN, state.MAX); - int direction = (isReversing) ? -1 : 1; - - for (int i = 0; i < jump; i++) { - pos = state.move(pos, direction); - - Point point = MAP.getPath(pos); - Map.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(); - } - - isReversing = false; - } - - private boolean hasAccident(Map.Circuit element, int jump) { - return element.isYRoad() && element.getValue() <= jump; - } - - public void reverse() { - isReversing = !isReversing; - } - - /** - * Consomme du carburant en fonction de l'état du jeu. - * - * @return cette même instance pour chaînage - */ - public void consumeFuel() { - fuel = state.fuelConsumption(fuel); - if (fuel < 0) - fuel = 0; // sécurité - } - - /** - * Exécute une "étape" de la voiture : avance d'une position aléatoire et - * consomme du carburant. - *

- * La progression est déterminée par un intervalle aléatoire fourni par l'état - * du jeu. - *

- */ - public void run() { - if (fuel > 0) { - move(); - consumeFuel(); - } - } - - /** - * @return la position actuelle (synonyme de getPosition) - */ - public int getPos() { - return pos; - } - - /** - * @return la couleur de la voiture - */ - public Color getColor() { - return COLOR; - } - - /** - * @return nom de la voiture - */ - public String getName() { - return NAME; - } -} - -/** - * DrunkCar = décorateur "pilote ivre". - * - * Idée : - * - Quand l'utilisateur demande "Accelerer", le pilote peut se tromper - * et faire "Rallentir" à la place (au hasard). - * - Pareil quand on demande "Rallentir". - * - * => On modifie seulement les actions utilisateur (accelerate/decelerate), - * sans modifier la classe Car. - */ -class DrunkCar extends Car { - private Car car; - - public DrunkCar(Car car) { - super(car); - this.car = car; - - } - - // 50% : fait la bonne action, - // 50% : fait l'inverse - private void mayReverse() { - if (RANDOM.nextBoolean()) { - car.reverse(); - } - } - - @Override - public void move() { - mayReverse(); - super.move(); - } -} - -/** - * Décorateur Sound : - * affiche un message sonore quand la voiture accélère. - */ -class SoundCar extends Car { - private Car car; - - public SoundCar(Car car) { - super(car); - this.car = car; - } - - @Override - public String accelerate() { - System.out.println("VROOOOM VROOOOOOM"); - return car.accelerate(); - } -} - -/** - * HybridCar = décorateur "voiture hybride". - * - * Idée : - * - La voiture avance à chaque tour - * - Mais elle consomme du carburant seulement 1 fois sur 2 - * => donc elle économise du carburant. - */ -class HybridCar extends Car { - private Car car; - private int energy = 100; // énergie batterie (0..100) - - public HybridCar(Car car) { - super(car); - this.car = car; - } - - /** pour afficher l'énergie dans le Dashboard */ - public int getEnergy() { - return energy; - } - - @Override - public void run() { - // 1) La voiture avance toujours - car.move(); - - // 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) { - car.consumeFuel(); - } - } - - @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; - - return msg; - } -} diff --git a/src/Main.java b/src/Main.java index ac3aef4..16c3a43 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,5 +1,8 @@ import java.util.List; +import model.Game; +import model.map.Map; + public class Main { public static void main(String[] args) throws InterruptedException { Map map = Game.GameFactory.defaultMap(); diff --git a/src/Rankboard.java b/src/Rankboard.java deleted file mode 100644 index 06c6829..0000000 --- a/src/Rankboard.java +++ /dev/null @@ -1,75 +0,0 @@ -import java.awt.BorderLayout; -import java.util.ArrayList; -import java.util.Comparator; - -import javax.swing.JLabel; - - -/** - * Rankboard est une vue graphique affichant le classement des voitures. - *

- * Elle hérite de GameView et met à jour dynamiquement le score de chaque voiture. - * Les scores sont triés du plus grand au plus petit. - *

- */ -public class Rankboard extends GameView -{ - /** Liste des voitures à afficher */ - ArrayList cars; - - /** Composant JLabel pour afficher le classement */ - private final 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); - } - - /** - * 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 - ArrayList cars_clone = new ArrayList<>(cars); - cars_clone.sort(Comparator.comparingInt(Car::getScore).reversed()); - - 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; - } -} diff --git a/src/Game.java b/src/model/Game.java similarity index 96% rename from src/Game.java rename to src/model/Game.java index bfb9b81..3fafb5f 100644 --- a/src/Game.java +++ b/src/model/Game.java @@ -1,6 +1,11 @@ +package model; + import java.awt.Color; import java.util.ArrayList; +import model.car.Car; +import model.map.Map; + /** * La classe {@link Game} représente le moteur principal du jeu. *

@@ -23,12 +28,6 @@ public class Game { public boolean apply(); } - /** - * {@link CarInfo} est une structure simple qui contient les informations - * nécessaires pour créer une voiture dans le jeu : son nom et sa couleur. - */ - private record CarInfo(String name, Color color) {} - /** * {@link VisualInfo} est un enregistrement (record) qui décrit une vue * graphique @@ -40,14 +39,14 @@ public class Game { * cette vue à l’écran. *

* - * @param type le type concret de la vue (classe héritant de {@link GameView}) + * @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, + private record VisualInfo(Class type, String title, int width, int height, int x, int y) { } @@ -135,7 +134,7 @@ public class Game { return this; } - public Builder addVisual(Class type, String title, + 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)); return this; @@ -143,7 +142,7 @@ public class Game { private void buildVisual(Game game) { for (VisualInfo visual : visuals) { - GameView view = null; + Game view = null; if (visual.type == Rankboard.class) { view = new Rankboard(game, visual.title, visual.width, diff --git a/src/model/car/BasicCar.java b/src/model/car/BasicCar.java new file mode 100644 index 0000000..790ec1a --- /dev/null +++ b/src/model/car/BasicCar.java @@ -0,0 +1,247 @@ +package model.car; + +import java.awt.Color; +import java.awt.Point; +import java.util.Random; + +import model.map.Circuit; +import model.map.Map; + +/** + * {@link BasicCar} représente une voiture qui avance sur un circuit en boucles. + * Chaque appel à {@link #run()} avance la voiture d'une certaine position et + * fait perdre de l'essence. + * Quand la position atteint la fin de la boucle, un nouveau tour est compté. + *

+ * La voiture consomme du carburant à chaque déplacement, et son score + * est calculé en fonction du nombre de tours complétés et de sa progression sur + * le tour actuel. + *

+ */ +public class BasicCar implements Car { + /** Générateur de nombres aléatoires pour simuler la progression */ + protected static final Random RANDOM = new Random(); + /** Couleur de la voiture pour l'affichage */ + private final Color COLOR; + /** Nom de la voiture */ + private final String NAME; + /** Position actuelle dans la boucle (entre 0 et loop) */ + private int pos = 0; + /** Nombre de tours complétés */ + private int round = 0; + /** Nombre de cases dans une boucle (doit être > 0) */ + private final Map MAP; + /** Carburant restant */ + private int fuel = 60; + + private int movement = 1; + + /** + * Construit une nouvelle voiture. + * + * @param name nom de la voiture + * @param color couleur de la voiture + * @param map + */ + public BasicCar(String name, Color color, Map map) { + this.NAME = name; + this.COLOR = color; + this.MAP = map; + } + + /** + * Référence à l'état du jeu, pour récupérer les paramètres comme la + * consommation + */ + private State state = State.NORMAL; + + private int damageRound = 0; + + public void setDamage() { + damageRound = 5; + state = State.DAMAGED; + } + + private boolean decreaseDamage() { + if (state.isDamaged()) { + if (--damageRound <= 0) { + state = state.onDamageEnd(); + } + return true; + } + return false; + } + + public BasicCar setState(State state) { + this.state = state; + 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). + */ + @Override + public String accelerate() { + // Si endommagée => l'énoncé dit qu'on ne peut pas bouger + if (state.isDamaged()) { + return "Voiture endommagée : impossible d'accélérer"; + } + State next = state.accelerate(); + + // Énoncé : en BOOST, si on accélère encore => message "déjà max" + if (state == State.BOOST && next == State.BOOST) { + return "Déjà à la vitesse maximale"; + } + state = next; + return ""; + } + + /** + * Clique sur "Rallentir" : change d'état et retourne un message (si + * nécessaire). + */ + @Override + public String decelerate() { + if (state.isDamaged()) { + return "Voiture endommagée : impossible de ralentir"; + } + + State next = state.decelerate(); + + // Énoncé : en STOPPED, si on ralentit encore => message "déjà arrêtée" + if (state == State.STOPPED && next == State.STOPPED) { + return "Déjà arrêtée"; + } + + state = next; + return ""; + } + + /** + * Fait avancer la voiture d'un certain nombre de positions. + *

+ * Si la position atteint la fin de la boucle, le compteur de tours est + * incrémenté et + * la position revient à zéro. + *

+ * + * @param move nombre de positions à avancer + * @return cette même instance (pour chaînage fluide) + */ + @Override + public void move() { + if (decreaseDamage()) { + System.out.println(NAME + " est en\taccident " + damageRound); + return; + } + + int jump = RANDOM.nextInt(state.MIN, state.MAX); + + 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); + + if (hasAccident(element, jump)) { + System.out.println(NAME + " a un\taccident"); + setDamage(); + return; + } + round = pos / MAP.getPathSize(); + } + } + + private boolean hasAccident(Circuit element, int jump) { + return element.isYRoad() && element.getValue() <= jump; + } + + /** + * Consomme du carburant en fonction de l'état du jeu. + * + * @return cette même instance pour chaînage + */ + @Override + public void consumeFuel() { + fuel = state.fuelConsumption(fuel); + if (fuel < 0) + fuel = 0; // sécurité + } + + /** + * Exécute une "étape" de la voiture : avance d'une position aléatoire et + * consomme du carburant. + *

+ * La progression est déterminée par un intervalle aléatoire fourni par l'état + * du jeu. + *

+ */ + @Override + public void run() { + if (fuel > 0) { + move(); + consumeFuel(); + } + } + + @Override + public void reverse(boolean active) { + movement = (active) ? -1 : 1; + } + + /** + * @return la position actuelle (synonyme de getPosition) + */ + public int getPos() { + return pos; + } + + /** + * @return la couleur de la voiture + */ + public Color getColor() { + return COLOR; + } + + /** + * @return nom de la voiture + */ + public String getName() { + return NAME; + } +} diff --git a/src/model/car/Car.java b/src/model/car/Car.java new file mode 100644 index 0000000..1ce5787 --- /dev/null +++ b/src/model/car/Car.java @@ -0,0 +1,23 @@ +package model.car; + +import java.awt.Color; + +public interface Car { + public String accelerate(); + + public String decelerate(); + + public void move(); + + public void consumeFuel(); + + public void run(); + + public void reverse(boolean active); + + public int getPos(); + + public Color getColor(); + + public String getName(); +} diff --git a/src/model/car/DrunkCar.java b/src/model/car/DrunkCar.java new file mode 100644 index 0000000..6b699e6 --- /dev/null +++ b/src/model/car/DrunkCar.java @@ -0,0 +1,75 @@ +package model.car; + +import java.awt.Color; +import java.util.Random; + +/** + * DrunkCar = décorateur "pilote ivre". + * + * Idée : + * - Quand l'utilisateur demande "Accelerer", le pilote peut se tromper + * et faire "Rallentir" à la place (au hasard). + * - Pareil quand on demande "Rallentir". + * + * => On modifie seulement les actions utilisateur (accelerate/decelerate), + * sans modifier la classe Car. + */ +public class DrunkCar implements Car { + /** 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; + } + + @Override + // 50% : fait la bonne action, + // 50% : fait l'inverse + public void move() { + car.reverse(RANDOM.nextBoolean()); + 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 new file mode 100644 index 0000000..c1c88da --- /dev/null +++ b/src/model/car/HybridCar.java @@ -0,0 +1,91 @@ +package model.car; + +import java.awt.Color; + +/** + * HybridCar = décorateur "voiture hybride". + * + * Idée : + * - La voiture avance à chaque tour + * - Mais elle consomme du carburant seulement 1 fois sur 2 + * => donc elle économise du carburant. + */ +public class HybridCar implements Car { + private Car car; + private int energy = 100; // énergie batterie (0..100) + + public HybridCar(Car car) { + this.car = car; + } + + /** pour afficher l'énergie dans le Dashboard */ + public int getEnergy() { + return energy; + } + + @Override + public void run() { + // 1) La voiture avance toujours + car.move(); + + // 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) { + car.consumeFuel(); + } + } + + @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; + + 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 new file mode 100644 index 0000000..8353fad --- /dev/null +++ b/src/model/car/SoundCar.java @@ -0,0 +1,62 @@ +package model.car; + +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 SoundCar(Car car) { + this.car = car; + } + + @Override + public String accelerate() { + 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/model/car/State.java b/src/model/car/State.java new file mode 100644 index 0000000..874466c --- /dev/null +++ b/src/model/car/State.java @@ -0,0 +1,146 @@ +package model.car; + +import java.util.List; + +public enum State { + /** + * L'état NORMAL du Vehicule avance selon un chiffre au alentour de 1 à 6 cases + * par tour. Il consomme 2 unités de carburant à chaque tour. Si l'on + * accelere, il passe à l'état BOOST. Si on Rallenti, il passe à l'état LOW. + */ + NORMAL(2, 1, 6) { + public State accelerate() { + return BOOST; + } + + public State decelerate() { + return LOW; + } + }, + + /** + * L'état BOOST du Vehicule avance selon un chiffre au alentour de 5 à 10 cases + * par tour. Il consomme 5 unités de carburant à chaque tour. Si l'on + * accelere, il reste sur son état et indique un message sur le tableau de bord. + * Si on Rallenti, il passe à l'état LOW. + */ + BOOST(5, 5, 10) { + public State accelerate() { + return this; + } + + public State decelerate() { + return NORMAL; + } + }, + + /** + * L'état LOW du Vehicule avance selon un chiffre au alentour de 1 à 3 cases + * par tour. Il consomme 1 unités de carburant à chaque tour. Si l'on + * accelere, il passe à l'état NORMAL. Si on Rallenti, il passe à l'état + * STOPPED. + */ + LOW(1, 1, 3) { + public State accelerate() { + return NORMAL; + } + + public State decelerate() { + return STOPPED; + } + }, + + /** + * L'état STOPPED du Vehicule n'avance pas. Il consomme aucune unités de + * carburant à chaque tour. Si l'on + * accelere, il passe à l'état LOW. Si on Rallenti, il reste sur son état et + * indique un message sur le tableau de bord. + */ + STOPPED(0, 0, 0) { + public State accelerate() { + return LOW; + } + + public State decelerate() { + return this; + } + + @Override + public int move(int pos, int jump) { + return pos; + } + + @Override + public int fuelConsumption(int fuel) { + return fuel; + } + }, + /** + * L'état STOPPED du Vehicule n'avance pas. Il consomme aucune unités de + * carburant à chaque tour. Il reste immobile. + */ + DAMAGED(0, 0, 0) { + public State accelerate() { + return this; + } + + public State decelerate() { + return this; + } + + @Override + public int move(int pos, int jump) { + return pos; + } + + @Override + public int fuelConsumption(int fuel) { + return fuel; + } + + @Override + public boolean isDamaged() { + return true; + } + + @Override + public State onDamageEnd() { + return LOW; + } + }; + // @formatter:on + + public final int FUELCOST; + public final int MAX; + public final int MIN; + + private State(int fuelCost, int min, int max) { + this.FUELCOST = fuelCost; + this.MAX = max + 1; + this.MIN = min; + } + + public List getInterval() { + return List.of(MIN, MAX); + } + + public int fuelConsumption(int fuel) { + return fuel - FUELCOST; + } + + public int move(int pos, int jump) { + return pos + jump; + } + + public boolean isDamaged() { + return false; + } + + public State onDamageEnd() { + return this; + } + + public abstract State accelerate(); + + public abstract State decelerate(); +} \ No newline at end of file diff --git a/src/model/map/Circuit.java b/src/model/map/Circuit.java new file mode 100644 index 0000000..5faa1ad --- /dev/null +++ b/src/model/map/Circuit.java @@ -0,0 +1,136 @@ +package model.map; + +/** + * Représente une cellule du circuit. + *

+ * Chaque cellule possède un type ({@link Cell}) etéventuellement une valeur + * numérique (par exemple pour indiquer une intensité, une vitesse, ou un + * identifiant de route). + *

+ */ +public class Circuit { + /** + * Cell est un enum + * représentant les différents types de + * cases qui composent le circuit de + * course. + */ + public static enum Cell { + /** + * Case hors piste, non + * praticable par les + * voitures. + */ + EMPTY, + + /** + * Case de route normale + * sur laquelle les voitures + * peuvent circuler. + */ + ROAD, + + /** + * Case correspondant à la + * ligne de départ du circuit. + */ + START, + + /** + * Case correspondant à la + * ligne d'arrivée du circuit. + */ + FINISH, + + /** + * Case de route jaune, plus + * d'information sur le + * livrable 2 + */ + YROAD; + } + + /** Type de la cellule (vide, route, départ, arrivée, etc.) */ + private final Cell type; + + /** Valeur associée à la cellule (optionnelle, dépend du type) */ + private int value; + + /** + * Construit une cellule avec un type défini et une valeur par défaut + * égale à l’indice ordinal du type. + * + * @param type le type de la cellule + */ + public Circuit(Cell type) { + this.type = type; + this.value = type.ordinal(); + } + + /** + * Construit une cellule avec un type et une valeur spécifique. + * + * @param type le type de la cellule + * @param value la valeur associée à la cellule + */ + public Circuit(Cell type, int value) { + this.type = type; + this.value = value; + } + + /** + * Modifie la valeur associée à la cellule. + * + * @param value la nouvelle valeur + * @return cette même instance (pour chaînage fluide) + */ + public Circuit setValue(int value) { + this.value = value; + return this; + } + + /** + * @return le type de la cellule + */ + public Cell getType() { + return type; + } + + /** + * @return la valeur associée à la cellule + */ + public int getValue() { + return value; + } + + /** + * Vérifie si la cellule est une route (ROAD ou YROAD). + * + * @return vrai si la cellule représente une route + */ + public boolean isRoad() { + return type == Cell.ROAD || type == Cell.YROAD; + } + + public boolean isYRoad() { + return type == Cell.YROAD; + } + + /** + * Vérifie si la cellule est un point de départ. + * + * @return vrai si la cellule représente le départ + */ + public boolean isStart() { + return type == Cell.START; + } + + /** + * Vérifie si la cellule est un point d’arrivée. + * + * @return vrai si la cellule représente la fin du circuit + */ + public boolean isFinish() { + return type == Cell.FINISH; + } +} diff --git a/src/Map.java b/src/model/map/Map.java similarity index 70% rename from src/Map.java rename to src/model/map/Map.java index a1f379d..18e0518 100644 --- a/src/Map.java +++ b/src/model/map/Map.java @@ -1,3 +1,4 @@ +package model.map; import java.awt.Point; import java.util.ArrayList; import java.util.function.Function; @@ -16,141 +17,6 @@ import java.util.function.Function; * */ public class Map { - /** - * Représente une cellule du circuit. - *

- * Chaque cellule possède un type ({@link Cell}) etéventuellement une valeur - * numérique (par exemple pour indiquer une intensité, une vitesse, ou un - * identifiant de route). - *

- */ - public static class Circuit { - /** - * Cell est un enum - * représentant les différents types de - * cases qui composent le circuit de - * course. - */ - public static enum Cell { - /** - * Case hors piste, non - * praticable par les - * voitures. - */ - EMPTY, - - /** - * Case de route normale - * sur laquelle les voitures - * peuvent circuler. - */ - ROAD, - - /** - * Case correspondant à la - * ligne de départ du circuit. - */ - START, - - /** - * Case correspondant à la - * ligne d'arrivée du circuit. - */ - FINISH, - - /** - * Case de route jaune, plus - * d'information sur le - * livrable 2 - */ - YROAD; - } - - /** Type de la cellule (vide, route, départ, arrivée, etc.) */ - private final Cell type; - - /** Valeur associée à la cellule (optionnelle, dépend du type) */ - private int value; - - /** - * Construit une cellule avec un type défini et une valeur par défaut - * égale à l’indice ordinal du type. - * - * @param type le type de la cellule - */ - public Circuit(Cell type) { - this.type = type; - this.value = type.ordinal(); - } - - /** - * Construit une cellule avec un type et une valeur spécifique. - * - * @param type le type de la cellule - * @param value la valeur associée à la cellule - */ - public Circuit(Cell type, int value) { - this.type = type; - this.value = value; - } - - /** - * Modifie la valeur associée à la cellule. - * - * @param value la nouvelle valeur - * @return cette même instance (pour chaînage fluide) - */ - public Circuit setValue(int value) { - this.value = value; - return this; - } - - /** - * @return le type de la cellule - */ - public Cell getType() { - return type; - } - - /** - * @return la valeur associée à la cellule - */ - public int getValue() { - return value; - } - - /** - * Vérifie si la cellule est une route (ROAD ou YROAD). - * - * @return vrai si la cellule représente une route - */ - public boolean isRoad() { - return type == Cell.ROAD || type == Cell.YROAD; - } - - public boolean isYRoad() { - return type == Cell.YROAD; - } - - /** - * Vérifie si la cellule est un point de départ. - * - * @return vrai si la cellule représente le départ - */ - public boolean isStart() { - return type == Cell.START; - } - - /** - * Vérifie si la cellule est un point d’arrivée. - * - * @return vrai si la cellule représente la fin du circuit - */ - public boolean isFinish() { - return type == Cell.FINISH; - } - } - /** * Tableau 2D représentant le circuit. * Chaque élément est une instance de {@link Circuit}. diff --git a/src/Dashboard.java b/src/visual/Dashboard.java similarity index 95% rename from src/Dashboard.java rename to src/visual/Dashboard.java index 5e1388a..74c2c79 100644 --- a/src/Dashboard.java +++ b/src/visual/Dashboard.java @@ -1,9 +1,14 @@ +package visual; + import java.awt.BorderLayout; import java.awt.GridLayout; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; +import model.Game; +import model.car.BasicCar; + /** * Dashboard représente une vue graphique pour une voiture spécifique. * @@ -39,7 +44,7 @@ public class Dashboard extends GameView { private final JButton decelerateButton = new JButton("Rallentir"); /** Voiture associée à ce dashboard */ - private final Car car; + private final BasicCar car; /** * Construit un dashboard pour une voiture donnée. @@ -52,7 +57,7 @@ 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, Car car, String title, int width, int height, int x, int y) { + public Dashboard(Game game, BasicCar car, String title, int width, int height, int x, int y) { super(game, "Dashboard: " + title, width, height, x, y); this.car = car; diff --git a/src/GameView.java b/src/visual/GameView.java similarity index 52% rename from src/GameView.java rename to src/visual/GameView.java index 5d8c907..6b1f502 100644 --- a/src/GameView.java +++ b/src/visual/GameView.java @@ -1,27 +1,29 @@ +package visual; + import java.awt.BorderLayout; import java.awt.GraphicsEnvironment; import javax.swing.JComponent; import javax.swing.JFrame; +import model.Game; + /** * Classe abstraite représentant une vue graphique du jeu. * Chaque GameView est associée à une fenêtre JFrame et s'inscrit dans * la liste globale des vues pour permettre des mises à jour centralisées. */ -public abstract class GameView extends JComponent implements Game.Observer -{ +public abstract class GameView extends JComponent implements Game.Observer { /** Fenêtre associée à cette vue */ protected final JFrame frame; protected final Game game; - /** + /** * Bloc statique exécuté au chargement de la classe pour vérifier * si le programme dispose d'un environnement graphique. */ static { - if (GraphicsEnvironment.isHeadless()) - { + if (GraphicsEnvironment.isHeadless()) { System.err.println("Aucun serveur d'affichage trouvé"); System.exit(1); } @@ -30,33 +32,32 @@ public abstract class GameView extends JComponent implements Game.Observer /** * Construit une nouvelle GameView avec une fenêtre JFrame. * - * @param title Titre de la fenêtre - * @param width Largeur de la fenêtre + * @param title Titre de la fenêtre + * @param width Largeur de la fenêtre * @param height Hauteur de la fenêtre - * @param x Position horizontale de la fenêtre à l'écran - * @param y Position verticale 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 */ - protected GameView(Game game, String title, int width, int height, int x, int y) - { + protected GameView(Game game, String title, int width, int height, int x, int y) { this.game = game; - // la fenetre - this.frame = new JFrame(title); - // position fenetre - frame.setLocation(x, y); - // taille fenetre - frame.setSize(width, height); - // bool pour resize la fenetre - frame.setResizable(true); - // le bouton close par defaut + // la fenetre + this.frame = new JFrame(title); + // position fenetre + frame.setLocation(x, y); + // taille fenetre + frame.setSize(width, height); + // bool pour resize la fenetre + frame.setResizable(true); + // le bouton close par defaut frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - // acceder a l'interieur de la fenetre - frame.setContentPane(this); - // visibilité de la fenetre + // acceder a l'interieur de la fenetre + frame.setContentPane(this); + // visibilité de la fenetre frame.setVisible(true); - // mettre un layout (la disposition des elements de la fenetre) - frame.setLayout(new BorderLayout()); + // mettre un layout (la disposition des elements de la fenetre) + frame.setLayout(new BorderLayout()); game.addObserver(this); - } + } } \ No newline at end of file diff --git a/src/visual/Rankboard.java b/src/visual/Rankboard.java new file mode 100644 index 0000000..69a8eba --- /dev/null +++ b/src/visual/Rankboard.java @@ -0,0 +1,75 @@ +package visual; + +import java.awt.BorderLayout; +import java.util.ArrayList; +import java.util.Comparator; + +import javax.swing.JLabel; + +import model.Game; +import model.car.BasicCar; + +/** + * Rankboard est une vue graphique affichant le classement des voitures. + *

+ * Elle hérite de GameView et met à jour dynamiquement le score de chaque + * voiture. + * Les scores sont triés du plus grand au plus petit. + *

+ */ +public class Rankboard extends GameView { + /** Liste des voitures à afficher */ + ArrayList cars; + + /** Composant JLabel pour afficher le classement */ + private final 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); + } + + /** + * 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 + ArrayList cars_clone = new ArrayList<>(cars); + cars_clone.sort(Comparator.comparingInt(BasicCar::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()); + } + + @Override + /** + * Méthode appelée par GameView.update(). + * Elle met à jour le classement affiché. + */ + public boolean apply() { + updateRankText(); + return true; + } +} diff --git a/src/Track.java b/src/visual/Track.java similarity index 91% rename from src/Track.java rename to src/visual/Track.java index 46a7a21..66dc6c8 100644 --- a/src/Track.java +++ b/src/visual/Track.java @@ -1,3 +1,4 @@ +package visual; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; @@ -5,6 +6,11 @@ import java.awt.Graphics; import java.awt.Point; import java.util.ArrayList; +import model.Game; +import model.car.BasicCar; +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. @@ -18,7 +24,7 @@ public class Track extends GameView { /** La carte du circuit */ private Map map; /** Liste des voitures à dessiner */ - private ArrayList cars; + private ArrayList cars; /** Échelle utilisée pour ajuster la taille des voitures dans les cellules */ private final int scale = 80; @@ -63,7 +69,7 @@ public class Track extends GameView { // Parcours de toutes les cellules du circuit for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { - Map.Circuit cell = this.map.getElement(x, y); + Circuit cell = this.map.getElement(x, y); drawCell(g, cell, new int[] {x, y}, cellSize); } } @@ -72,7 +78,7 @@ public class Track extends GameView { /** * Retourne la couleur d'une cellule selon son type. */ - private Color getCellColor(Map.Circuit.Cell cell) { + private Color getCellColor(Circuit.Cell cell) { return switch (cell) { case ROAD -> Color.GRAY; case START -> Color.YELLOW; @@ -85,7 +91,7 @@ public class Track extends GameView { /** * Retourne le caractère à afficher pour une cellule du circuit. */ - private char getCellChar(Map.Circuit cell) { + private char getCellChar(Circuit cell) { return switch (cell.getType()) { case ROAD -> '\0'; case START -> 'D'; @@ -101,7 +107,7 @@ public class Track extends GameView { */ private void drawCars(Graphics g, int[] cellCoord) { int size = 1; - for (Car c : cars) { + for (BasicCar c : cars) { int i = c.getPos(); Point p = this.map.getPath(i); int[] ncoord = new int[] {p.x, p.y}; @@ -113,7 +119,7 @@ public class Track extends GameView { /** * Dessine une cellule complète avec son contenu (caractère et voitures). */ - private void drawCell(Graphics g, Map.Circuit cell, int[] coord, int[] cellCoord) { + private void drawCell(Graphics g, Circuit cell, int[] coord, int[] cellCoord) { drawBlock(g, cell, coord, cellCoord); char c = getCellChar(cell); @@ -171,7 +177,7 @@ public class Track extends GameView { /** * Dessine une cellule de base selon son type et ajoute un contour noir. */ - private void drawBlock(Graphics g, Map.Circuit cell, int[] coord, int[] cellCoord) { + private void drawBlock(Graphics g, Circuit cell, int[] coord, int[] cellCoord) { int x = coord[0]; int y = coord[1]; @@ -181,7 +187,7 @@ public class Track extends GameView { switch (cell.getType()) { case YROAD: // dessine le bloc de route - g.setColor(getCellColor(Map.Circuit.Cell.ROAD)); + g.setColor(getCellColor(Circuit.Cell.ROAD)); g.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight); // dessine le sous bloc de route