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; } }