Cartes
Javass – étape 1

1 Introduction

Le but principal de cette première étape est d'écrire les classes nécessaires à la représentation des cartes d'un jeu de Jass. Comme toutes les descriptions d'étapes de ce projet, celle-ci commence par une introduction aux concepts nécessaires à sa réalisation, avant de présenter leur mise en œuvre en Java.

Avant d'aller plus loin, il vous est toutefois demandé de lire les guides Travailler en groupe, Synchroniser son travail et Configurer Eclipse. Ils vous donneront des informations vous permettant de bien débuter.

1.1 Cartes de Jass

Le Jass se joue avec un jeu de 36 cartes. Chaque carte est caractérisée par :

  1. sa couleur ou enseigne : pique (♠), cœur (♥), carreau (♦) ou trèfle (♣),
  2. son rang : 6, 7, 8, 9, 10, valet, dame, roi ou as.

L'image ci-dessous montre les 36 cartes d'un jeu de Jass. Chaque ligne contient les cartes d'une couleur, ordonnées par rang. Comme il s'agit d'un jeu de cartes d'origine anglaise, les lettres attachées au valet, à la dame et au roi correspondent à leur nom anglais, respectivement jack, queen et king.

all-cards.png

Figure 1 : Les 36 cartes d'un jeu de Jass

Notez que le terme couleur, qu'on utilise généralement pour désigner l'enseigne d'une carte, peut prêter à confusion. En effet, les cartes sont principalement colorées en deux couleurs — rouge et noir — mais il existe un total de quatre couleurs (au sens d'enseigne) différentes — ♠, ♥, ♦ et ♣. Dans la suite du projet, nous utiliserons toujours le terme de couleur dans le sens d'enseigne.

1.2 Comparaison des cartes

Pour savoir qui remporte un pli, il est nécessaire de pouvoir comparer deux cartes et déterminer laquelle est supérieure à l'autre.

Cela n'est toutefois pas aussi facile qu'il n'y paraît, car certaines paires de cartes ne sont pas comparables entre elles, c-à-d qu'aucune des deux n'est supérieure à l'autre. Les cartes de Jass ne sont donc que partiellement ordonnées, au sens mathématique.

Pour simplifier les choses, il convient de distinguer deux cas :

  1. les deux cartes à comparer sont de couleur identique, auquel cas elles sont toujours comparables — l'une des deux est forcément supérieure à l'autre,
  2. les deux cartes à comparer sont de couleur différente, auquel cas elles ne sont comparables que si l'une d'entre elles est de couleur atout.

Ces deux cas sont présentés dans les sous-sections qui suivent.

1.2.1 Cartes de même couleur

Les cartes d'une même couleur sont toutes comparables entre elles. Au sens mathématique, elles sont donc totalement ordonnées. Toutefois, cet ordre est différent pour les cartes d'atout et pour les autres cartes.

Pour les cartes d'une couleur autre que l'atout, l'ordre est l'ordre « naturel » présenté à la figure 1 plus haut : 6 est la carte la plus faible, suivie de 7, et ainsi de suite jusqu'à l'as.

Pour les cartes d'atout, l'ordre est légèrement différent puisque le 9 est supérieur à l'as, et le valet est supérieur au 9.

La table ci-dessous résume ces deux ordres :

Atout 6, 7, 8, 10, dame, roi, as, 9, valet
Autre 6, 7, 8, 9, 10, valet, dame, roi, as

1.2.2 Cartes de couleur différente

Deux cartes de couleur différente ne sont comparables entre elles que si l'une des deux est une carte d'atout. Dans ce cas, la carte d'atout est toujours supérieure à l'autre carte, indépendamment du rang. Sinon, les cartes ne sont pas comparables, et aucune n'est donc supérieure à l'autre.

Par exemple, si l'atout est pique, alors le 6 de pique est supérieur à l'as de trèfle (par exemple) ; par contre, l'as de trèfle et le 6 de cœur ne sont pas comparables, c-à-d qu'aucune de ces deux cartes n'est supérieure à l'autre.

1.3 Valeur des cartes

Chaque carte a une valeur, qui est le nombre de points qu'elle rapporte à l'équipe qui la possède à la fin d'un tour. En général, la valeur ne dépend pas de la couleur, mais il y a deux exceptions : le neuf et le valet d'atout — souvent appelés respectivement le nel et le bour — valent plus de points que ceux des autres couleurs.

La table ci-dessous donne la valeur de chaque carte, en fonction de s'il s'agit d'une carte d'atout ou non.

Rang Atout Autre
6 0 0
7 0 0
8 0 0
9 14 0
10 10 10
Valet 20 2
Dame 3 3
Roi 4 4
As 11 11
Total 62 30

Sachant qu'il y a en tout quatre couleurs mais une seule qui est atout, la valeur totale de toutes les cartes du jeu est donc 62 + 3 × 30 = 152 points.

1.4 Représentation des cartes en Java

Avant de décrire les classes permettant de manipuler les cartes de Jass en Java, il convient de réfléchir à la manière de les représenter.

Dans un langage orienté-objets comme Java, la manière naturelle de représenter les cartes consiste bien entendu à en faire une classe (ou un type énuméré) dont les principaux attributs sont la couleur et le rang.

Cette représentation est à la fois très naturelle et agréable à utiliser, mais elle souffre d'un petit défaut qui peut être gênant dans certaines situations : chaque carte occupe une place mémoire assez importante pour le peu d'information qu'elle contient. En effet, un objet doté des deux attributs mentionnés (couleur et rang) occupe environ une centaine de bits, et est lui-même manipulé au moyen d'une référence qui occupe 32 ou 64 bits supplémentaires.

Dans la quasi-totalité des cas, cette utilisation mémoire ne pose absolument aucun problème, et la représentation des cartes sous forme d'objets est parfaitement adaptée.

Toutefois, dans ce projet, nous aimerions également avoir à disposition une représentation aussi compacte que possible des cartes et autres données liées au jeu. La raison en est que le joueur simulé — sujet de l'étape 6 — décidera quelle carte jouer en évaluant un très grand nombre de parties aléatoires. Pour qu'il puisse faire cela de manière efficace, il est utile qu'il ait à disposition des représentations compacte des données nécessaires : cartes, ensembles de cartes, plis, etc.

Pour cette raison, dans cette étape et celles qui suivront, nous développerons systématiquement deux représentations de toutes les données de base du jeu de Jass :

  1. une représentation empaquetée sous forme d'entier de 32 ou 64 bits, compacte et efficace mais peu agréable à l'usage,
  2. une représentation sous forme d'objets, moins compacte et efficace mais très agréable à l'usage.

La seconde représentation se basera sur la première, et sera systématiquement utilisée dans le reste du projet, sauf dans les cas où les performances sont très importantes — typiquement dans le joueur simulé.

Dans le cas des cartes, la représentation empaquetée consiste en un entier de type int (32 bits) dont seuls les 6 bits de poids faible sont utilisés, les autres valant toujours 0. Les 4 bits de poids faible (d'index 0 à 3) contiennent le rang de la carte ainsi empaquetée (0 pour 6, 1 pour 7, …, 8 pour as) tandis que les 2 bits suivants (d'index 4 à 5) contiennent sa couleur (0 pour ♠, 1 pour ♥, etc.). Cette représentation est résumée dans la table ci-dessous :

Bits 31 à 6 5 à 4 3 à 0
Contenu 0 couleur rang

Par exemple, l'as de cœur est représenté par l'entier int valant 0…011000 : les 4 bits de poids faible (1000) désignent le rang as, tandis que les 2 bits suivants (01) désignent la couleur cœur (♥).

2 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. Cette section énumère les classes et interfaces à écrire pour cette étape.

En plus des classes permettant de manipuler les deux représentations des cartes (empaquetée et objet), quelques classes et interfaces « utilitaires » sont à réaliser dans le cadre de cette étape : Preconditions, qui offre des méthodes de validation d'arguments ; Bits32, qui offre des méthodes de manipulation d'entiers de 32 bits ; et Jass, qui contient des constantes liées au Jass.

Notez que toutes les classes et interfaces de ce projet appartiendront au paquetage ch.epfl.javass ou à l'un de ses sous-paquetages.

Attention : jusqu'à l'étape 6 du projet, 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. Vous pouvez par contre définir des méthodes et attributs privés si cela vous semble judicieux.

2.1 Classe Preconditions

Fréquemment, les méthodes d'un programme s'attendent à ce que leurs arguments satisfassent certaines conditions. Par exemple, une méthode déterminant la valeur maximale d'un tableau d'entiers s'attend à ce que ce tableau ne soit pas vide.

De telles conditions sont souvent appelées préconditions 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 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 pourrait commencer ainsi :

int max(int[] array) {
  if (! (array.length > 0))
    throw new IllegalArgumentException();
  // … reste du code
}

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 :

import static ch.epfl.javass.Preconditions.checkArgument;
int max(int[] array) {
  checkArgument(array.length > 0);
  // … reste du code
}

Cette classe est nommée Preconditions et appartient au paquetage ch.epfl.javass. Elle est publique et finale et n'offre rien d'autre que deux méthodes statiques décrites 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 à un ensemble de méthodes statiques. Dans la suite du projet, nous définirons plusieurs autres classes du même types, que nous appellerons dès maintenant classes non instanciables.

Les deux méthodes publiques (et statiques) de la classe Preconditions sont :

  • void checkArgument(boolean b), qui lève l'exception IllegalArgumentException si son argument est faux, et ne fait rien sinon,
  • int checkIndex(int index, int size), qui lève l'exception IndexOutOfBoundsException si l'index donné est négatif ou supérieur ou égal à size, et le retourne tel quel sinon.

2.2 Classe Bits32

La classe Bits32 du paquetage ch.epfl.javass.bits, publique, finale et non instanciable, contient des méthodes statiques permettant de travailler sur des vecteurs de 32 bits stockés dans des valeurs de type int.

En plus de son constructeur par défaut privé qui la rend non instanciable, elle offre les méthodes publiques et statiques suivantes :

  • int mask(int start, int size), qui retourne un entier dont les bits d'index allant de start (inclus) à start + size (exclus) valent 1, les autres valant 0, ou lève l'exception IllegalArgumentException si start et size ne désignent pas une plage de bits valide (c-à-d comprise entre 0 et 32, inclus),
  • int extract(int bits, int start, int size), qui retourne une valeur dont les size bits de poids faible sont égaux à ceux de bits allant de l'index start (inclus) à l'index start + size (exclus), ou lève l'exception IllegalArgumentException si start et size ne désignent pas une plage de bits valide,
  • int pack(int v1, int s1, int v2, int s2), qui retourne les valeurs v1 et v2 empaquetées dans un entier de type int, v1 occupant les s1 bits de poids faible, et v2 occupant les s2 bits suivants, tous les autres bits valant 0 ; lève l'exception IllegalArgumentException si l'une des tailles n'est pas comprise entre 1 (inclus) et 31 (inclus), si l'une des valeurs occupe plus de bits que sa taille, ou si la somme des tailles est supérieure à 32.

Finalement, la classe Bits32 offre encore deux autres versions de la méthode pack, qui permettent d'empaqueter respectivement 3 et 7 valeurs. Notez que comme chaque valeur à empaqueter nécessite deux arguments — la valeur elle-même et sa taille —, ces deux variantes de la méthode pack possèdent respectivement 6 et 14 (!) arguments.

2.2.1 Conseils de programmation

Les conseils de programmation qui suivent ont pour but de vous aider à écrire la classe Bits32. Lisez-les tous avant de vous lancer dans la programmation !

  1. Taille des entiers

    Pour valider les arguments des différentes méthodes, n'écrivez pas directement la constante 32 dans votre code, utilisez plutôt la constante Integer.SIZE fournie par la bibliothèque Java, et qui vaut justement 32. Votre code sera ainsi plus clair !

  2. Validation des arguments de pack

    Pour valider les arguments des trois méthodes pack, il est conseillé d'écrire une méthode privée auxiliaire prenant une paire valeur/taille et la validant en vérifiant que :

    • la taille est comprise entre 1 et 31 (inclus),
    • la valeur n'occupe pas plus de bits que le nombre spécifié par la taille.

    Cette méthode peut ensuite être appliquée à chaque paire individuellement.

2.3 Interface Jass

L'interface Jass du paquetage ch.epfl.javass.jass, publique, contient un certain nombre de constantes entières de type int, liées au jeu de Jass. Il s'agit de :

  • HAND_SIZE, le nombre de cartes dans une main au début d'un tour (9),
  • TRICKS_PER_TURN, le nombre de plis dans un tour de jeu (9),
  • WINNING_POINTS, le nombre de points nécessaire à une victoire (1000),
  • MATCH_ADDITIONAL_POINTS, le nombre de points additionnels obtenus par une équipe remportant la totalité des plis d'un tour (100),
  • LAST_TRICK_ADDITIONAL_POINTS, le nombre de points additionnels obtenu par l'équipe remportant le dernier pli (5).

Notez que ces constantes ne seront pas utiles dans cette étape, mais le seront dans le futur. Ne vous souciez pas du fait que vous ne comprenez peut-être pas encore le sens de certaines de ces constantes.

2.4 Classe Card (squelette)

La classe Card du paquetage ch.epfl.javass.jass, publique, finale et immuable, représente une carte d'un jeu de 36 cartes.

Cette classe ne sera décrite précisément qu'à la section 2.8 ci-dessous, mais il est à ce stade nécessaire de la créer afin de pouvoir y imbriquer les types énumérés Color et Rank dont la description suit. Pour l'instant, contentez-vous donc de déclarer cette classe sans lui ajouter aucun contenu.

2.5 Type énuméré Card.Color

Le type énuméré (énumération) Color, publique et imbriqué dans la classe Card, représente la couleur d'une carte. Il ne possède que quatre valeurs qui sont, dans l'ordre :

  1. SPADE, pique (),
  2. HEART, cœur (),
  3. DIAMOND, carreau (),
  4. CLUB, trèfle ().

En plus de ces quatre valeurs, le type énuméré Color possède deux attributs statiques et finaux, qui sont :

  • ALL, de type List<Color>, est une liste immuable contenant toutes les valeurs du type énuméré, dans leur ordre de déclaration (voir les conseils de programmation ci-dessous),
  • COUNT, de type int, est le nombre de valeurs du type énuméré, c-à-d 4 dans ce cas.

Finalement, le type énuméré Color redéfinit la méthode toString de Object afin qu'elle retourne le symbole correspondant à la couleur. Par exemple, l'extrait de programme ci-dessous doit imprimer le caractère ♥ :

System.out.println(Card.Color.HEART.toString());

2.5.1 Conseils de programmation

  1. Membres de types énumérés

    Pour mémoire, les types énumérés Java peuvent posséder des attributs, des méthodes et des constructeurs (privés uniquement). Cela peut être p.ex. utile pour associer à chaque couleur son symbole, retourné par la méthode toString.

    L'exemple ci-dessous, dont vous pouvez vous inspirer, rappelle la syntaxe :

    public enum Season {
      SPRING("printemps"),
      SUMMER("été"),
      AUTUMN("automne"),
      WINTER("hiver");
    
      private String frenchName;
      private Season(String frenchName) {
        this.frenchName = frenchName;
      }
    
      public String frenchName() {
        return frenchName;
      }
    }
    

    Pour plus de détails, reportez-vous au cours du semestre précédent, en particulier à la leçon de la semaine 11 sur les classes imbriquées et types énumérés.

  2. Attribut ALL

    L'attribut (statique) ALL de Color contient une liste de toutes les valeurs du type énuméré, dans l'ordre de déclaration.

    La notion de liste sera examinée en détail dans le cours sur les collections, mais pour l'instant vous pouvez considérer qu'une liste n'est rien d'autre qu'un tableau dynamique. En d'autres termes, vous pouvez considérer que le type List<Color> est équivalent au type ArrayList<Color>.

    Une caractéristique importante de la liste ALL est qu'elle doit être immuable, c-à-d que son contenu ne doit jamais changer. La bibliothèque Java offre la méthode statique unmodifiableList dans la classe Collections, qui permet (en gros) de rendre une liste existante immuable. On peut donc l'utiliser pour s'assurer que la liste contenue dans ALL soit immuable, en écrivant quelque chose comme :

    public enum Color {
      SPADE, /* … autres valeurs */;
    
      public static final List<Color> ALL =
        Collections.unmodifiableList(/* à compléter */);
    
      // … autres membres (COUNT, toString)
    }
    

    Il reste encore à savoir comment obtenir la liste de toutes les valeurs du type énuméré, dans l'ordre de déclaration. Il serait bien entendu possible de la construire en ajoutant une à une les différentes couleurs à un tableau dynamique, mais une solution bien plus simple existe.

    Pour mémoire, les types énumérés Java offrent tous une méthode statique nommée values retournant la totalité des valeurs du type énuméré, dans l'ordre de déclaration. Le seul problème de cette méthode est qu'elle retourne un tableau Java « normal » (et non pas un tableau dynamique), qui est de plus recréé à chaque appel. Il est néanmoins possible d'utiliser la méthode statique asList de la classe Arrays pour transformer un tel tableau en liste !

    En d'autres termes, en combinant les différentes méthodes susmentionnées, il est très facile de définir l'attribut ALL :

    List<Color> ALL =
      Collections.unmodifiableList(Arrays.asList(values()));
    
  3. Caractères représentant les couleurs de cartes

    Il existe des caractères représentant chacune des quatre couleurs des cartes, mais il n'est pas très facile de savoir comment les entrer sur un clavier d'ordinateur.

    En Java, il est possible d'utiliser une notation spéciale pour les désigner, composée des deux caractères \u suivis d'un code hexadécimal (c-à-d en base 16) à 4 chiffres représentant le caractère1.

    Par exemple, l'extrait de programme Java ci-dessous affiche le caractère correspondant à la couleur pique, dont le code hexadécimal est 2660 :

    System.out.println("\u2660");           // imprime ♠
    

    Notez bien que les 6 caractères \u2660 ne représentent en réalité qu'un seul caractère ! Pour vous en convaincre, vous pouvez imprimer la longueur de la chaîne ci-dessus :

    System.out.println("\u2660".length());  // imprime 1
    

    Les codes correspondants au trois autres couleurs sont ceux qui suivent, à savoir 2661 (cœur, ♡), 2662 (carreau, ♢) et 2663 (trèfle, ♣). Notez qu'il existe également des versions pleines des symboles pour les couleurs cœur et carreau, dont les codes sont 2665 (♥) et 2666 (♦). Vous êtes libres d'utiliser la variante que vous préférez.

2.6 Type énuméré Card.Rank

Le type énuméré Rank, publique et imbriqué dans la classe Card, représente le rang d'une carte. Il possède, dans l'ordre, les valeurs suivantes :

  1. SIX, le 6,
  2. SEVEN, le 7,
  3. EIGHT, le 8,
  4. NINE, le 9,
  5. TEN, le 10,
  6. JACK, le valet,
  7. QUEEN, la dame,
  8. KING, le roi,
  9. ACE, l'as.

Tout comme Card.Color, le type énuméré Card.Rank offre les deux champs finaux statiques ALL et COUNT contenant respectivement la liste de tous les rangs (dans l'ordre ci-dessus) et le nombre de rangs.

De plus, Rank offre la méthode suivante :

  • int trumpOrdinal(), qui retourne la position, comprise entre 0 et 8, de la carte d'atout ayant ce rang dans l'ordre des cartes d'atout donné à la section 1.2.1, à savoir : 0 pour SIX, 1 pour SEVEN, …, 7 pour NINE et 8 pour JACK (trump signifie atout en anglais).

Finalement, Rank redéfinit la méthode toString pour qu'elle retourne une représentation compacte du rang. Pour les rangs de SIX à TEN il s'agit simplement de la représentation du rang sous forme de nombre (6 à 10), et pour les suivants de la première lettre du nom anglais du rang (J, Q, K et A).

2.7 Classe PackedCard

La classe PackedCard du paquetage ch.epfl.javass.jass, publique, finale et non instanciable, contient des méthodes statiques permettant de manipuler des cartes d'un jeu de Jass empaquetées dans un entier de type int selon la technique décrite à la section 1.4.

En plus des méthodes décrites plus bas, la classe PackedCard offre un champ de type int, public, final et statique, nommé INVALID. Il contient la valeur binaire 111111, qui ne représente pas une carte empaquetée valide. Cette valeur est utile dans certains cas, comme nous le verrons lors d'étapes ultérieures.

La classe PackedCard possède les méthodes (publiques et bien entendu statiques) ci-dessous :

  • boolean isValid(int pkCard), qui retourne vrai ssi la valeur donnée est une carte empaquetée valide, c-à-d si les bits contenant le rang contiennent une valeur comprise entre 0 et 8 (inclus) et si les bits inutilisés valent tous 0,
  • int pack(Card.Color c, Card.Rank r), qui retourne la carte empaquetée de couleur et rang donnés,
  • Card.Color color(int pkCard), qui retourne la couleur de la carte empaquetée donnée,
  • Card.Rank rank(int pkCard), qui retourne le rang de la carte empaquetée donnée,
  • boolean isBetter(Card.Color trump, int pkCardL, int pkCardR), qui retourne vrai ssi la première carte donnée est supérieure à la seconde, sachant que l'atout est trump ; notez que cela implique que cette méthode retourne faux si les deux cartes ne sont pas comparables,
  • int points(Card.Color trump, int pkCard), qui retourne la valeur de la carte empaquetée donnée, sachant que l'atout est trump,
  • String toString(int pkCard), qui retourne une représentation de la carte empaquetée donnée sous forme de chaîne de caractères composée du symbole de la couleur et du nom abrégé du rang.

2.7.1 Conseils de programmation

  1. Validation des arguments

    Notez que comme PackedCard est une classe de bas niveau destinée à être utilisée dans les situations où les performances sont importantes, ses méthodes ne font aucune validation classique des arguments, p.ex. au moyen des méthodes de Preconditions.

    Il est toutefois fortement conseillé de faire cette validation, mais en utilisant des assertions, qui ont l'avantage d'être désactivables pour obtenir les meilleures performances possibles. Par exemple, la méthode color pourrait valider son argument ainsi :

    public static Card.Color color(int pkCard) {
      assert isValid(pkCard);
      // … reste du code
    }
    
  2. Méthodes auxiliaires

    La définition de certaines des méthodes de PackedCard peut être simplifiée grâce à :

    1. la méthode ordinal définie par toutes les énumérations Java et qui retourne la position d'un membre de cette énumération dans celle-ci,
    2. la classe Bits32, dont plusieurs méthodes sont utiles ici.

2.8 Classe Card

Les types énumérés Color et Rank, ainsi que la classe PackedCard, ayant été écrits, il est maintenant possible d'écrire le contenu de la classe Card elle-même.

La classe Card n'offre pas de constructeur public mais elle offre deux méthodes statiques permettant de créer d'obtenir des instances, qui sont :

  • static Card of(Color c, Rank r), qui retourne la carte de couleur et de rang donnés,
  • static Card ofPacked(int packed), qui retourne la carte dont packed est la version empaquetée, ou lève l'exception IllegalArgumentException si cet argument ne représente pas une carte empaquetée valide.

L'avantage de définir des méthodes statiques plutôt que deux constructeurs séparés est que les méthodes statiques peuvent porter un nom évoquant mieux leur but. Bien entendu, la classe Card possède néanmoins un constructeur, utilisé par les deux méthodes ci-dessus, mais il est privé !

En dehors de ces méthodes statiques de construction, la classe Card offre les méthodes suivantes, qui pour la plupart correspondent directement aux méthodes de même nom de la classe PackedCard :

  • int packed(), qui retourne la version empaquetée de la carte,
  • Color color(), qui retourne la couleur de la carte,
  • Rank rank(), qui retourne le rang de la carte,
  • boolean isBetter(Color trump, Card that), qui retourne vrai ssi le récepteur (c-à-d la carte à laquelle on applique la méthode) est supérieur à la carte passée en argument, sachant que l'atout est trump,
  • int points(Color trump), qui retourne la valeur de la carte, sachant que l'atout est trump.

Finalement, la classe Card redéfinit trois méthodes héritées de Object, à savoir :

  • boolean equals(Object thatO), qui retourne vrai ssi le récepteur est égal à l'objet passé en argument, c-à-d s'il représente la même carte,
  • int hashCode(), qui retourne la même valeur que la méthode packed, pour des raisons décrites ci-dessous,
  • String toString(), qui retourne une représentation textuelle de la carte, la même que celle retournée par la méthode toString de PackedCard.

L'utilité de la méthode hashCode sera décrite ultérieurement dans le cours. Pour l'instant, il suffit de savoir que l'entier qu'elle retourne doit obligatoirement être le même pour deux objets considérés comme égaux par equals, et idéalement être différent pour deux objets considérés comme différents par equals. Il est facile de voir que l'entier représentant la carte empaquetée satisfait ces deux conditions, raison pour laquelle nous l'utilisons ici.

2.8.1 Conseils de programmation

Il va sans dire que la manière la plus raisonnable de mettre en œuvre la classe Card consiste à lui faire stocker comme unique attribut l'entier int représentant la version empaquetée de la carte. Cette solution a deux avantages importants :

  1. les instances de Card utilisent moins de mémoire que si on avait choisi de stocker la couleur et le rang de manière séparée, sous forme d'objets de type Card.Color et Card.Rank respectivement,
  2. les méthodes de Card sont triviales à écrire, puisqu'elles ne font généralement qu'appeler la méthode de PackedCard qui leur correspond directement.

Notez qu'il est spécifié plus haut que la classe Card doit être immuable, ce qui signifie que ses instances ne doivent pas changer une fois créées. Dès lors, l'attribut contenant la version empaquetée de la carte doit être déclaré final, afin de garantir cela.

La notion d'immuabilité sera examinée plus en détails ultérieurement dans le cours, mais souvenez-vous pour l'instant que la totalité des champs d'une classe immuable doivent être finaux.

2.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 tout d'abord les importer dans votre projet en suivant les indications d'importation d'archive Zip dans Eclipse, puis ajouter la bibliothèque JUnit à votre projet, en suivant les explications à ce sujet.

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

3 Résumé

Pour cette étape, vous devez :

  • configurer Eclipse selon les indications données dans le document consacré à ce sujet,
  • écrire les classes, interfaces et types énumérés Preconditions, Bits32, Jass, Card.Color, Card.Rank, PackedCard et Card selon 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 22 février 2019 à 16h30, 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

1

Ce code est ce que l'on nomme le point de code Unicode du caractère en question, notion qui sera examinée plus en détail dans une leçon ultérieure.