Document ID: /fineweb-2-swissfilter-quality_10-filterrobots/filtered/07123.jsonl.gz/597

1 Introduction
Le but principal de cette première étape est d'écrire les classes représentant trois éléments importants du jeu : les gares, les cartes et les billets.
Comme toutes les descriptions d'étapes de ce projet, celle-ci commence par une introduction aux concepts nécessaires à sa réalisation (§2), suivie d'une présentation de leur mise en œuvre en Java (§3).
2 Concepts
2.1 Réseau
Comme cela a été expliqué dans l'introduction au projet, une partie de tCHu se joue sur une carte de la Suisse et des pays voisins, visible ci-dessous1.
Un certain nombre de villes, toutes suisses, sont nommées sur cette carte. Elles sont reliées entre elles par un réseau ferroviaire représenté par des cases rectangulaires colorées placées les unes derrière les autres. Ce réseau s'étend également aux pays voisins — Allemagne, Autriche, Italie et France — mais les villes des pays voisins ne sont pas nommées et sont simplement représentées par un drapeau du pays.
2.2 Gares
Une gare (station en anglais) est un point du réseau ferroviaire de tCHu. Il en existe de deux types : celles qui correspondent à des villes suisses, et celles qui correspondent à des villes non spécifiées dans un pays voisin.
La principale caractéristique d'une gare est son nom. Pour celles situées en Suisse, ce nom est simplement celui de leur ville. Pour celles situées dans un pays voisin, ce nom est celui du pays en question. Ainsi, les quatre gares situées en France portent toutes le nom de « France », mais il s'agit néanmoins de gares différentes.
La carte de tCHu comporte 34 gares en Suisse, et 17 dans les pays voisins : 5 en Allemagne, 3 en Autriche, 5 en Italie et 4 en France. Il y a donc 51 gares au total.
2.3 Cartes wagon/locomotive
Les joueurs de tCHu ont en main en certain nombre de cartes wagon (car cards) et de cartes locomotive (locomotive cards), qu'ils utilisent pour s'emparer des routes du réseau ferroviaire, comme nous le verrons à l'étape suivante.
Les cartes wagon sont ornées d'un dessin de wagon et colorées d'une des huit couleurs utilisées dans le jeu, à savoir : noir, violet, bleu, vert, jaune, orange, rouge, ou blanc. Chaque carte wagon d'une couleur donnée existe en 12 exemplaires, et le jeu comporte donc un total de 96 (8×12) cartes wagon.
Les cartes locomotive sont ornées d'un dessin de locomotive. Bien que ces cartes soient aussi colorées (en turquoise), leur couleur est purement décorative et ne joue aucun rôle dans le jeu, contrairement à celle des cartes wagon. Le jeu comporte 14 cartes locomotive.
Au total, le jeu comporte donc 110 (96+14) cartes wagon et locomotive, que nous appellerons désormais simplement « les cartes ».
2.4 Billets
En plus des cartes, les joueurs ont également en main un certain nombre de billets (tickets) représentant chacun un objectif à réaliser. Tout comme un billet de train réel, un billet de tCHu est constitué d'un point de départ (p.ex. Lausanne) et d'un point d'arrivée (p.ex. Saint-Gall). De plus, un billet de tCHu vaut un certain nombre de points, qui sont ceux que le joueur qui le possède gagnera (resp. perdra) à la fin de la partie s'il a réussi (resp. échoué) à relier le point de départ au point d'arrivée avec ses propres wagons.
Il existe de trois types de billets tCHu, décrits en détail plus bas :
- ville à ville,
- ville à pays,
- pays à pays.
Le jeu de tCHu comporte un total de 46 billets : 34 billets ville à ville, 4 billets ville à pays, et 8 billets pays à pays. Les billets pays à pays existent en deux exemplaires chacun, tandis que tous les autres billets sont uniques.
2.4.1 Billet ville à ville
Un billet « ville à ville » relie simplement deux villes (suisses) entre elles, par exemple Lausanne et Saint-Gall. Un nombre de points fixe est attribué à ce type de billet, qui correspond souvent à la longueur du plus court chemin reliant les deux villes, 13 dans le cas de Lausanne et de Saint-Gall.
Un joueur possédant un tel billet et réussissant effectivement à relier les deux villes avant la fin de la partie gagne les points correspondants, 13 dans notre exemple. S'il ne parvient pas à relier les villes, il perd ces 13 points, qui sont soustraits de son total.
2.4.2 Billet ville à pays
Un billet « ville à pays » relie une ville suisse à chacun des pays voisins (France, Allemagne, Autriche et Italie). Les points attribués à ce type de billet dépendent du pays destination. Par exemple, dans le cas du billet allant de Berne à un pays voisin, la liaison avec l'Allemagne vaut 6 points, celle avec l'Autriche 11, celle avec l'Italie 8 et celle avec la France 5. Là aussi, ces points correspondent généralement à la longueur du plus court chemin reliant la ville à la gare la plus proche du pays en question.
Un joueur possédant un billet ville à pays et réussissant effectivement à relier la ville à au moins une gare d'un des pays voisins gagne le nombre de points maximum correspondant. S'il ne réussit pas à relier la ville avec une gare d'un des pays voisins, il perd le nombre minimum de points.
Dans l'exemple ci-dessus, un joueur ayant réussi à relier Berne à la France — c-à-d à au moins une gare française, peu importe laquelle — et à l'Italie gagne 8 points, pour la connexion avec l'Italie. Par contre, s'il ne réussit à relier Berne à aucun des pays voisins, il ne perd que 5 points, le minimum possible — pour la connexion avec la France.
2.4.3 Billet pays à pays
Un billet « pays à pays » relie un pays voisin de départ donné à chacun des autres pays voisins, par exemple la France à chacun des pays restants (Allemagne, Autriche et Italie). Là aussi, le nombre de points dépend du pays. Dans l'exemple du billet partant de la France, la connexion avec l'Allemagne vaut 5 points, celle avec l'Autriche 14 et celle avec l'Italie 11.
Les points gagnés ou perdus à la fin de la partie par un joueur possédant un tel billet sont déterminés de la même manière que pour les billets ville à pays.
2.4.4 Trajets
La description des billets donnée ci-dessus peut faire penser qu'il existe trois types de billets totalement différents. Or ce n'est pas vraiment le cas, et il est tout à fait possible d'unifier ces trois types de billets en un seul.
Pour ce faire, il suffit de considérer qu'un billet est constitué d'une liste de ce que nous appellerons des trajets (trips). Un trajet est constitué de deux gares à relier et d'un nombre de points.
Ainsi, un billet ville à ville est toujours constitué d'un unique trajet, dont les deux gares et les points sont bien entendu ceux du billet lui-même. Par exemple, le billet Lausanne - Saint-Gall décrit plus haut comporte un seul trajet, qui va de Lausanne à Saint-Gall et vaut 13 points.
Un billet ville à pays est constitué quant à lui d'un assez grand nombre de trajets dont :
- la première gare est toujours la gare de la ville,
- la seconde gare est une gare d'un pays voisin,
- le nombre de points est celui attribué au pays voisin en question.
Par exemple, le billet de Berne à un pays voisin est constitué de 17 trajets, un pour chaque gare se trouvant dans un pays voisin, parmi lesquels on trouve :
- un trajet de Berne à la gare française voisine de La Chaux-de-Fonds, valant 5 points,
- un trajet de Berne à la gare française voisine de Delémont, valant aussi 5 points,
- un trajet de Berne à la gare allemande voisine de Bâle, valant 6 points,
- etc.
Un billet pays à pays est constitué également de plusieurs trajets, qui vont de chacune des gares du pays de départ à chacune des gares d'un autre pays voisin. Par exemple, le billet France - pays voisin est constitué d'un total de 52 (4×13) trajets, chacun d'entre eux reliant l'une des 4 gares françaises à l'une des 13 gares des autres pays voisins (Allemagne, Autriche et Italie).
2.4.5 Représentation
Dans la version originale des Aventuriers du Rail, les billets sont représentés par des petites cartes de Suisse sur lesquelles figurent une représentation graphique des trajets et de leurs points. Par exemple, le billet allant de Berne à un pays voisin est représenté ainsi :
Pour tCHu, dans un soucis de simplicité, nous utiliserons une représentation textuelle des billets, construite de la manière suivante.
La représentation textuelle d'un billet ville à ville est constituée du nom de la ville de départ, d'un tiret entouré d'espaces, du nom de la ville d'arrivée, et du nombre de points du billet entre parenthèses et précédé d'un espace. Par exemple, la représentation textuelle du billet allant de Lausanne à Saint-Gall et valant 13 points est :
Lausanne - Saint-Gall (13)
La représentation textuelle d'un billet ville à pays est similaire, mais le nom de la gare d'arrivée est remplacé par une liste des différents pays d'arrivée entre accolades, chaque pays étant suivi du nombre de points correspondant. Les pays sont triés par ordre alphabétique (Allemagne, Autriche, France, Italie). Par exemple, le billet allant de Berne aux pays voisins a la représentation textuelle suivante :
Berne - {Allemagne (6), Autriche (11), France (5), Italie (8)}
La représentation textuelle d'un billet pays à pays est similaire. Par exemple, le billet allant de la France à l'un des autres pays voisins a pour représentation textuelle :
France - {Allemagne (5), Autriche (14), Italie (11)}
3 Mise en œuvre Java
Les concepts importants pour cette étape ayant été introduits, il est temps de décrire leur mise en œuvre en Java.
En plus des classes permettant de représenter les éléments de jeu expliqués ci-dessus, une classe « utilitaire » est à réaliser :
Preconditions, qui offre une méthode de validation d'argument.
Notez que toutes les classes et interfaces de ce projet appartiendront au paquetage
ch.epfl.tchu ou à l'un de ses sous-paquetages. Par exemple, à l'exception de la classe
Preconditions, toutes les classes de cette étape appartiendront au sous-paquetage
ch.epfl.tchu.game, destiné à contenir les classes représentant la logique du jeu.
Attention : jusqu'à l'étape 6 du projet (incluse), vous devez suivre à la lettre les instructions qui vous sont données dans l'énoncé, et vous n'avez pas le droit d'apporter la moindre modification à l'interface publique des classes, interfaces et types énumérés décrits.
En d'autres termes, vous ne pouvez pas ajouter des classes, interfaces ou types énumérés publics à votre projet, ni ajouter des attributs ou méthodes publics aux classes, interfaces et types énumérés décrits dans l'énoncé. Vous pouvez par contre définir des méthodes et attributs privés si cela vous semble judicieux.
Cette restriction sera levée dès l'étape 7.
3.1 Installation de Java
Avant de commencer à programmer, il faut vous assurer que la version 11 de Java est bien installée sur votre ordinateur, car c'est celle qui sera utilisée pour ce projet.
Si vous n'avez pas encore installé cette version, rendez-vous sur le site Web du projet AdoptOpenJDK, téléchargez le programme d'installation correspondant à votre système d'exploitation (macOS, Linux ou Windows), et exécutez-le. Ensuite, si vous utilisez Eclipse plutôt qu'IntelliJ, lisez le guide Configurer Eclipse et suivez les indications qui s'y trouvent.
3.2 Classe
Preconditions
Fréquemment, les méthodes d'un programme demandent que leurs arguments satisfassent certaines conditions. Par exemple, une méthode déterminant la valeur maximale d'un tableau d'entiers exige que ce tableau contienne au moins un élément.
De telles conditions sont souvent appelées préconditions (preconditions) car elles doivent être satisfaites avant l'appel d'une méthode : c'est à l'appelant de s'assurer qu'il n'appelle la méthode qu'avec des arguments valides.
En Java, la convention veut que chaque méthode vérifie, autant que possible, ses préconditions et lève une exception — souvent
IllegalArgumentException — si l'une d'entre elles n'est pas satisfaite. Par exemple, une méthode
max calculant la valeur maximale d'un tableau d'entiers, et exigeant logiquement que celui-ci contienne au moins un élément, pourrait commencer ainsi :
int max(int[] array) { if (! (array.length > 0)) throw new IllegalArgumentException(); // … reste du code }
(Notez au passage que la méthode
max ne déclare pas qu'elle lève potentiellement
IllegalArgumentException au moyen d'une clause
throws, car cette exception est de type unchecked.)
La première classe à réaliser dans le cadre de ce projet a pour but de faciliter l'écriture de telles préconditions. En l'utilisant, la méthode ci-dessus pourrait être simplifiée ainsi :
int max(int[] array) { Preconditions.checkArgument(array.length > 0); // … reste du code }
Cette classe est nommée
Preconditions et appartient au paquetage
ch.epfl.tchu. Elle est publique et finale et n'offre rien d'autre que la méthode
checkArgument décrite plus bas. Elle a toutefois la particularité d'avoir un constructeur par défaut privé :
public final class Preconditions { private Preconditions() {} // … méthodes }
Le but de ce constructeur privé est de rendre impossible la création d'instances de la classe, puisque cela n'a clairement aucun sens — elle ne sert que de conteneur à une méthode statique. Dans la suite du projet, nous définirons plusieurs autres classes du même type, que nous appellerons dès maintenant classes non instanciables.
La méthode publique (et statique) offerte par la classe
Preconditions est :
void checkArgument(boolean shouldBeTrue), qui lève l'exception
IllegalArgumentExceptionsi son argument est faux, et ne fait rien sinon.
3.3 Type énuméré
Color
Le type énuméré
Color du paquetage
ch.epfl.tchu.game, public, représente les huit couleurs utilisées dans le jeu pour colorer les cartes wagon et les routes. Les valeurs de ce type énuméré sont, dans l'ordre :
BLACK(noir),
VIOLET(violet)
BLUE(bleu),
GREEN(vert),
YELLOW(jaune),
ORANGE(orange),
RED(rouge),
WHITE(blanc).
En plus de ces valeurs, le type énuméré
Color offre deux attributs publics, statiques et finaux, qui sont :
List<Color> ALL, une liste immuable contenant la totalité des valeurs du type énuméré, dans leur ordre de définition (voir les conseils de programmation ci-dessous),
int COUNT, qui contient le nombre de valeurs du type énuméré.
3.3.1 Conseils de programmation
La définition de l'attribut
ALL peut vous poser quelques problèmes, car le concept de liste, représenté en Java par l'interface
java.util.List, n'a pas encore été vu au cours. Toutefois, vous avez vu au premier semestre le concept de tableau dynamique (
ArrayList), qui est une sorte de liste. Pour l'instant, vous pouvez donc admettre que le type
List est synonyme du type
ArrayList, et que les méthodes offertes par une valeur de type
ArrayList le sont aussi par une valeur de type
List.
Sachant cela, il n'est pas très difficile de définir l'attribut
ALL en s'aidant des deux méthodes statiques suivantes :
- la méthode
valuesdéfinie pour tous les types énumérés et retournant un tableau contenant les éléments du type énuméré dans leur ordre de définition — vue à la leçon 10 du cours du semestre précédent,
- la méthode
ofde l'interface
List, qui retourne une liste immuable ayant les mêmes éléments que le tableau qu'on lui passe en argument.
L'extrait de programme ci-dessous, dont vous pouvez vous inspirer, montre comment la méthode
of de
List peut être utilisée pour obtenir une liste (
seasons) des noms de saisons stockés dans un tableau (
seasonsArray) :
String[] seasonsArray = new String[] { "printemps", "été", "automne", "hiver" }; List<String> seasons = List.of(seasonsArray);
Une fois l'attribut
ALL défini, l'attribut
COUNT est trivial à définir puisque sa valeur n'est rien d'autre que la longueur de la liste
ALL, qui s'obtient au moyen de la méthode
size de
List, que vous avez certainement déjà utilisée avec
ArrayList.
3.4 Type énuméré
Card
Le type énuméré
Card du paquetage
ch.epfl.tchu.game, public, représente les différents types de cartes du jeu, à savoir les huit types de cartes wagon (un par couleur), et le type de carte locomotive.
Les éléments de ce type énuméré sont, dans l'ordre :
BLACK(wagon noir),
VIOLET(wagon violet),
BLUE(wagon bleu),
GREEN(wagon vert),
YELLOW(wagon jaune),
ORANGE(wagon orange),
RED(wagon rouge),
WHITE(wagon blanc),
LOCOMOTIVE(locomotive).
En plus de ces valeurs,
Card offre les attributs
ALL et
COUNT, définis de la même manière que dans
Color, ainsi que l'attribut public, final et statique suivant :
List<Card> CARS, qui contient uniquement les cartes wagon, dans l'ordre de définition, c-à-d de
BLACKà
WHITE.
En dehors des attributs, le type énuméré
Card offre la méthode publique et statique suivante :
Card of(Color color), qui retourne le type de carte wagon correspondant à la couleur donnée.
Finalement, le type énuméré
Card offre la méthode publique suivante :
Color color(), qui retourne la couleur du type de carte auquel on l'applique s'il s'agit d'un type wagon, ou
nulls'il s'agit du type locomotive.
3.4.1 Conseils de programmation
Il peut être judicieux de stocker, dans les instances du type énuméré
Card, la couleur qui leur correspond, ou
null pour le type locomotive. Cela implique d'ajouter un attribut au type énuméré, ainsi qu'un constructeur l'initialisant. Si vous ne vous souvenez plus comment faire cela, consultez la leçon 10 du cours du semestre passé.
3.5 Classe
Station
La classe
Station du paquetage
ch.epfl.tchu.game, publique, finale et immuable, représente une gare. Elle offre un unique constructeur public :
Station(int id, String name), qui construit une gare ayant le numéro d'identification et le nom donnés, ou lève
IllegalArgumentExceptionsi le numéro d'identification est strictement négatif (< 0).
Le numéro d'identification d'une gare est un numéro unique compris entre 0 et le nombre de gares du réseau moins un. Par exemple, étant donné que le réseau de tCHu comporte 51 gares, elles seront identifiées par un numéro allant de 0 à 50. La valeur exacte du numéro d'identification importe peu, la seule chose qui compte est que chaque gare ait son propre numéro. L'utilité de ce numéro deviendra claire à l'étape 4.
Il faut noter que, comme nous l'avons vu à la §2.2, les gares qui se trouvent dans un pays voisin de la Suisse portent le nom de leur pays. Il est donc tout à fait possible que plusieurs gares différentes portent le même nom, mais elles doivent néanmoins avoir chacune un numéro d'identification différent.
En plus du constructeur, la classe
Station offre deux méthodes publiques qui donnent accès aux deux attributs de la gare, à savoir :
int id(), qui retourne le numéro d'identification de la gare,
String name(), qui retourne le nom de la gare.
Finalement, la classe
Station redéfinit la méthode
toString pour qu'elle retourne le nom de la gare.
3.6 Interface
StationConnectivity
L'interface
StationConnectivity du paquetage
ch.epfl.tchu.game, publique, représente ce que nous appellerons la « connectivité » du réseau d'un joueur, c-à-d le fait que deux gares du réseau de tCHu soient reliées ou non par le réseau du joueur en question.
Cette interface n'offre qu'une seule méthode, publique et abstraite, qui est :
boolean connected(Station s1, Station s2), qui retourne vrai si et seulement si les gares données sont reliées par le réseau du joueur.
Un exemple simple mais peu réaliste d'une classe implémentant cette interface est :
public class FullConnectivity implements StationConnectivity { @Override public boolean connected(Station s1, Station s2) { return true; } }
Cette classe considère que n'importe quelle gare est connectée à n'importe quelle autre, puisque sa méthode
connected retourne toujours vrai ! Elle correspond donc au réseau d'un joueur qui aurait réussi à connecter toutes les gares du réseau de tCHu entre elles, ce qui n'est probablement pas possible.
Bien entendu, la classe implémentant
StationConnectivity que nous utiliserons dans le projet sera plus sophistiquée — et plus utile — que cela. Elle sera en effet capable de déterminer s'il est possible d'aller d'une gare quelconque du réseau à une autre en empruntant uniquement des routes appartenant à un joueur donné. Grâce à cette classe, il sera possible de déterminer les objectifs qui auront été atteints par les différents joueurs, et de calculer ainsi les points qu'ils méritent.
Cette classe plus sophistiquée est toutefois trop complexe pour être écrite dans le cadre de cette étape, et nous ne l'écrirons qu'à l'étape 4. Grâce à l'existence de l'interface
StationConnectivity ci-dessus, il nous est toutefois déjà possible d'écrire du code ayant besoin de cette classe sans qu'elle existe pour autant. Ce code est bien entendu celui déterminant le nombre de points que rapportent les billets, qui est réparti entre les classes
Trip et
Ticket décrites ci-après.
3.7 Classe
Trip
La classe
Trip du paquetage
ch.epfl.tchu.game, publique, finale et immuable, représente ce que nous avons appelé un trajet. Elle offre un unique constructeur public :
Trip(Station from, Station to, int points), qui construit un nouveau trajet entre les deux gares données et valant le nombre de points donné, ou lève
NullPointerExceptionsi l'une des deux gares est nulle et
IllegalArgumentExceptionsi le nombre de points n'est pas strictement positif (> 0).
En plus de ce constructeur, la classe
Trip offre une méthode publique et statique :
List<Trip> all(List<Station> from, List<Station> to, int points), qui retourne la liste de tous les trajets possibles allant d'une des gares de la première liste (
from) à l'une des gares de la seconde liste (
to), chacun valant le nombre de points donné ; lève
IllegalArgumentExceptionsi l'une des listes est vide, ou si le nombre de points n'est pas strictement positif.
Finalement, la classe
Trip offre les méthodes publiques suivantes :
Station from(), qui retourne la gare de départ du trajet,
Station to(), qui retourne la gare d'arrivée du trajet,
int points(), qui retourne le nombre de points du trajet,
int points(StationConnectivity connectivity), qui retourne le nombre de points du trajet pour la connectivité donnée.
La seconde variante de la méthode
points retourne le nombre de points du trajet si la méthode
connected de la connectivité qu'on lui passe retourne vrai lorsqu'on l'applique aux deux gares du trajet — ce qui signifie qu'elles sont bien connectées —, et la négation de ce nombre de points sinon.
3.7.1 Conseils de programmation
- Constructeur
Prenez garde au fait que le constructeur doit obligatoirement lever l'exception
NullPointerExceptionsi l'une des gares qu'on lui passe vaut
null. Il est donc faux de simplement stocker ces valeurs dans des attributs ainsi :
public final class Trip { private final Station from; // … autres attributs public Trip(Station from, Station to, int points) { this.from = from; // Faux, car accepte null // … reste du code } }
Au lieu de cela, il faut explicitement vérifier que
from(et
to) n'est pas
nullavant de le stocker, ce qui peut se faire très facilement au moyen de la méthode
requireNonNullde la classe
Objects(avec un s à la fin !), ainsi :
public final class Trip { private final Station from; // … autres attributs public Trip(Station from, Station to, int points) { this.from = Objects.requireNonNull(from); // … reste du code } }
- Méthode
all
La méthode
alldoit retourner une valeur de type
List. Comme
Listest une interface, il n'est pas possible d'en créer directement une instance. Au lieu de cela, il faut créer (et retourner) une instance d'une classe qui implémente cette interface, comme
ArrayListque vous connaissez déjà.
Notez que pour parcourir les éléments des listes
fromet
to, vous pouvez utiliser la boucle for-each que vous avez vue au premier semestre, et probablement déjà utilisée pour parcourir, entre autres, des tableaux dynamiques.
3.8 Classe
Ticket
La classe
Ticket du paquetage
ch.epfl.tchu.game, publique, finale et immuable, représente un billet. Pour des raisons qui deviendront claires plus tard, cette classe implémente l'interface
Comparable<Ticket> et sa définition ressemble donc à ceci :
public final class Ticket implements Comparable<Ticket> { // … corps de la classe }
Pour l'instant, il n'est pas nécessaire de comprendre pourquoi la classe
Ticket implémente cette interface. Il suffit de savoir que cela implique qu'elle doit offrir une méthode de comparaison nommée
compareTo, décrite plus bas.
La classe
Ticket offre les deux constructeurs suivants :
Ticket(List<Trip> trips), qui construit un billet constitué de la liste de trajets donnée, ou lève
IllegalArgumentExceptionsi celle-ci est vide, ou si toutes les gares de départ des trajets n'ont pas le même nom,
Ticket(Station from, Station to, int points), qui construit un billet constitué d'un unique trajet, dont les attributs sont
from,
toet
points.
Notez que le premier de ces constructeurs est ce que nous nommerons le constructeur principal, et c'est lui qui se charge d'initialiser les attributs de la classe. Le second constructeur est quant à lui ce que nous nommerons un constructeur secondaire, ce qui signifie qu'il consiste en un simple appel au constructeur principal, qui se fait au moyen de la notation
this(…).
En plus de ces constructeurs, la classe
Ticket offre les méthodes publiques suivantes, au sujet desquels des conseils de programmation vous sont donnés plus bas :
String text(), qui retourne la représentation textuelle du billet, telle que décrite à la §2.4.5,
int points(StationConnectivity connectivity), qui retourne le nombre de points que vaut le billet, sachant que la connectivité donnée est celle du joueur possédant le billet,
int compareTo(Ticket that), qui compare le billet auquel on l'applique (
this) à celui passé en argument (
that) par ordre alphabétique de leur représentation textuelle, et retourne un entier strictement négatif si
thisest strictement plus petit que
that, un entier strictement positif si
thisest strictement plus grand que
that, et zéro si les deux sont égaux.
De plus, la classe
Ticket redéfinit la méthode
toString de
Object afin qu'elle retourne la même valeur que la méthode
text.
3.8.1 Conseils de programmation
- Méthode
text
La méthode
textdoit retourner la représentation textuelle du billet selon la spécification donnée à la §2.4.5. Le calcul de cette représentation étant non trivial, il est conseillé de le placer dans une méthode privée (et idéalement statique) nommée p.ex.
computeText. Cette méthode peut ensuite être appelée depuis le constructeur, et son résultat stocké dans un attribut privé (et final) de la classe. La méthode
textn'a ensuite plus qu'à retourner la valeur de cet attribut.
La méthode
computeTextn'en reste pas moins difficile à écrire avec vos connaissances actuelles. Nous vous donnons donc ci-après quelques indications au sujet de classes et de méthodes qui pourront vous être utiles, à vous de voir comment les combiner.
Pour commencer, une méthode utile pour écrire
computeTextest la méthode statique
joinde la classe
String. On lui passe en premier argument une chaîne qui sert de séparateur et en second argument une collection de chaînes, p.ex. un tableau dynamique de type
ArrayList<String>ou encore un ensemble de type
TreeSet<String>décrit ci-dessous. Elle retourne une chaîne constituée des chaînes de la collection, séparées les unes des autres par le séparateur.
L'extrait de programme ci-dessous illustre son utilisation en réutilisant la liste
seasonsintroduite à la §3.3.1.
List<String> seasons = …; // voir plus haut // s est égale à "printemps, été, automne, hiver" String s = String.join(", ", seasons);
En plus de la méthode
joinde
String, la classe
TreeSetest aussi très utile à la rédaction de
computeText. Cette classe possède la plupart des méthodes de
ArrayListet ses instances se comportent presque de la même manière que celles de
ArrayListmais avec deux différences importantes :
TreeSetn'admet pas les doublons, ce qui signifie que si un même élément est ajouté plusieurs fois à une instance de
TreeSetau moyen de la méthode
add, cet élément n'apparaît qu'une fois lorsqu'on parcourt ensuite les éléments de cette instance, p.ex. au moyen de la boucle for-each,
- lors du parcours des éléments d'une instance de
TreeSet, les éléments apparaissent triés par ordre croissant, et pas dans l'ordre dans lequel on les a insérés au moyen de la méthode
add.
L'extrait de programme suivant illustre ces différences ajoutant les mêmes éléments à une instance de
ArrayListet de
TreeSetpuis en utilisant la méthode
joinpour obtenir une chaîne de leur contenu. Observez bien la différence entre les deux chaînes !
ArrayList<String> l = new ArrayList<>(); TreeSet<String> s = new TreeSet<>(); for (String w: List.of("to","be","or","not","to","be")) { l.add(w); s.add(w); } // ls est égale à "to be or not to be" String ls = String.join(" ", l); // ss est égale à "be not or to" String ss = String.join(" ", s);
Finalement, une dernière méthode utile à la rédaction de
computeTextest la méthode statique
formatde
String. Elle prend en premier argument une chaîne de caractères, que l'on nomme la chaîne de formatage (format string), puis un nombre variable d'arguments. Elle retourne une chaîne qui est identique à la chaîne de formatage qu'on lui a passé, si ce n'est que chaque occurrence de la chaîne
%sa été remplacée par la représentation textuelle de l'argument correspondant.
L'extrait de programme ci-dessous illustre l'utilisation de la méthode
formatpour construire une chaîne :
String n = "Lausanne"; int p = 139720; // s est égale à "Lausanne compte 139720 habitants" String s = String.format("%s compte %s habitants", n, p);
Comme vous l'aurez constaté, telle que présentée ci-dessus la méthode
formatne semble pas offrir de gros avantage par rapport à l'opérateur de concaténation de chaîne (
+). En effet, l'exemple ci-dessus peut aussi s'écrire ainsi :
String s = n + " compte " + p + " habitants";
En réalité, la méthode
formatpermet de formater les arguments qu'on lui passe de manière sophistiquée, ce que ne permet pas l'opérateur de concaténation. Nous n'utiliserons pas ces possibilités de formatage dans le cadre de ce projet, raison pour laquelle nous ne les présentons pas ici, mais les personnes intéressées par les détails pourront se reporter à la documentation de la classe
Formatter.
Cela dit, nous aurons plus tard de bonnes raisons de préférer l'utilisation de la méthode
formatà l'opérateur de concaténation, raison pour laquelle il semble judicieux que vous preniez l'habitude de l'utiliser dès maintenant.
-
- Méthode
compareTo
Il est spécifié ci-dessus que la méthode
compareTodoit comparer les billets
thiset
thatpar ordre alphabétique de leur représentation textuelle. Pour ce faire, il faut savoir que la classe
Stringoffre également une méthode
compareTo, qui compare la chaîne à laquelle on l'applique (
this) à celle qu'on lui passe en argument, et retourne un entier négatif quelconque si la première vient avant la seconde dans l'ordre alphabétique, zéro si les deux sont égales, et un entier positif quelconque sinon.
L'extrait de programme suivant montre trois utilisations de cette méthode, qui correspondent à ses trois types de valeurs de retour possible :
"EPFL".compareTo("ETHZ"); // retourne un entier négatif "EPFL".compareTo("EPFL"); // retourne l'entier 0 "ETHZ".compareTo("EPFL"); // retourne un entier positif
3.9 Tests
Pour vous aider à démarrer ce projet, nous vous fournissons exceptionnellement une archive Zip contenant des tests unitaires JUnit pour cette étape.
Pour pouvoir utiliser ces tests, il vous faut :
3.10 Documentation
Une fois les tests exécutés avec succès, il vous reste à documenter la totalité des entités publiques (classes, attributs et méthodes) définies dans cette étape, au moyen de commentaires Javadoc, comme décrit dans le guide consacré à ce sujet. Vous pouvez écrire ces commentaires en français ou en anglais, en fonction de votre préférence, mais vous ne devez utiliser qu'une seule langue pour tout le projet.
4 Résumé
Pour cette étape, vous devez :
- installer la version 11 (et ni une plus récente, ni une plus ancienne !) de Java sur votre ordinateur, ainsi que la dernière version d'IntelliJ ou d'Eclipse,
- si vous utilisez Eclipse, le configurer selon les indications données dans le document consacré à ce sujet,
- écrire les classes, interfaces et types énumérés
Preconditions,
Color,
Card,
Station,
StationConnectivity,
Tripet
Ticketselon les indications données plus haut,
- vérifier que les tests que nous vous fournissons s'exécutent sans erreur, et dans le cas contraire, corriger votre code,
- documenter la totalité des entités publiques que vous avez définies,
- (optionnel mais fortement recommandé) rendre votre code au plus tard le 26 février 2021 à 17h00, via le système de rendu.
Ce premier rendu n'est pas noté, mais celui de la prochaine étape le sera. Dès lors, il est vivement conseillé de faire un rendu de test cette semaine afin de se familiariser avec la procédure à suivre.
Notes de bas de page
Bien qu'assez réaliste, cette carte comporte deux inexactitudes : premièrement, le Liechtenstein est intégré à la Suisse ; et deuxièmement, par manque de place, La Chaux-de-Fonds est située en France, bien qu'il s'agisse d'une ville suisse.