Ensembles de cartes
Javass – étape 3
1 Introduction
Le but de cette troisième étape est d'écrire les classes permettant de manipuler des ensembles de cartes, aussi bien sous forme empaquetée que sous forme d'objets.
1.1 Ensembles de cartes
Dans le cadre de ce projet, un ensemble de cartes n'est rien d'autre qu'un ensemble — au sens mathématique — de cartes de Jass. Ces ensembles sont utiles dans plusieurs situations, par exemple pour représenter les cartes qu'un joueur a en main. Les ensembles de cartes définis ici auront pour particularité d'être triés, dans le sens où les cartes y seront stockées dans un ordre bien défini.
1.2 Ensembles de cartes empaquetés
Sachant qu'il existe en tout 36 cartes différentes au Jass, tout ensemble de cartes peut être représenté au moyen de 36 bits. Il suffit pour cela d'attribuer un bit à chaque carte, et de dire que ce bit vaut 1 si et seulement si la carte correspondante appartient à l'ensemble.
En Java, cela signifie qu'un ensemble de cartes quelconque peut être représenté
au moyen d'une unique valeur de type long
(64 bits).
Pour associer les bits aux différentes cartes, nous utiliserons une technique très simple consistant à faire correspondre à une carte donnée le bit dont l'index est égal à sa version empaquetée. Par exemple, comme nous l'avons vu à l'étape 1, l'as de cœur est représenté par l'entier valant 0…011000
, c-à-d 24. Dès lors, le bit 24 d'une valeur de type long
représentant un ensemble de cartes empaqueté sera utilisé pour déterminer si l'as de cœur fait partie de l'ensemble (bit 24 = 1) ou non (bit 24 = 0).
Étant donné la manière dont les cartes sont empaquetées, il est facile de voir qu'à chaque couleur correspond un paquet de 16 bits dans un ensemble empaqueté de la sorte :
Bits | 63 à 48 | 47 à 32 | 31 à 16 | 15 à 0 |
---|---|---|---|---|
Contenu | ♣ | ♦ | ♥ | ♠ |
et que chacun de ces paquets de 16 bits est organisé ainsi :
Bits | 15 à 9 | 8 | 7 | … | 1 | 0 |
---|---|---|---|---|---|---|
Contenu | inutilisés (0) | as | roi | … | 7 | 6 |
Une caractéristique importante de cette représentation est que les cartes y sont organisées par couleur, et triées dans l'ordre. Par exemple, le bit 0 correspond au 6 de pique, le bit 1 au 7 de pique, et ainsi de suite. Dès lors, en parcourant les bits par poids croissant, on parcourt les cartes de l'ensemble dans un ordre bien défini et naturel pour un être humain : d'abord les cartes de pique, du 6 à l'as, ensuite les cartes de cœur, du 6 à l'as, et ainsi de suite.
C'est la raison pour laquelle nous avons choisi d'empaqueter les cartes de la manière décrite à l'étape 1, en plaçant la couleur dans les bits de poids fort et le rang dans ceux de poids faible.
2 Mise en œuvre Java
2.1 Classe PackedCardSet
La classe PackedCardSet
du paquetage ch.epfl.javass.jass
, publique, finale et non instanciable, possède des méthodes permettant de manipuler des ensembles de cartes empaquetés dans des valeurs de type long
(64 bits).
Elle possède deux attributs publics, statiques et finaux contenant des ensembles de cartes souvent utiles, qui sont :
long EMPTY
, l'ensemble de cartes vide,long ALL_CARDS
, l'ensemble des 36 cartes du jeu de Jass.
De plus, la classe PackedCardSet
offre les méthodes publiques suivantes :
boolean isValid(long pkCardSet)
, qui retourne vrai ssi la valeur donnée représente un ensemble de cartes empaqueté valide, c-à-d si aucun des 28 bits inutilisés ne vaut 1,long trumpAbove(int pkCard)
, qui retourne l'ensemble des cartes strictement plus fortes que la carte empaquetée donnée, sachant qu'il s'agit d'une carte d'atout ; par exemple, appliquée à l'as de cœur, elle doit retourner un ensemble contenant deux éléments, le neuf et le valet de cœur, car ces deux cartes sont les seules à être strictement plus fortes que l'as de cœur lorsque cœur est atout ; attention, cette méthode doit s'exécuter rapidement (voir les conseils de programmation plus bas),long singleton(int pkCard)
, qui retourne l'ensemble de cartes empaqueté contenant uniquement la carte empaquetée donnée,boolean isEmpty(long pkCardSet)
, qui retourne vrai ssi l'ensemble de cartes empaqueté donné est vide,int size(long pkCardSet)
, qui retourne la taille de l'ensemble de cartes empaqueté donné, c-à-d le nombre de cartes qu'il contient,int get(long pkCardSet, int index)
, qui retourne la version empaquetée de la carte d'index donné de l'ensemble de cartes empaqueté donné, la carte d'index 0 étant celle correspondant au bit de poids le plus faible valant 1,long add(long pkCardSet, int pkCard)
, qui retourne l'ensemble de cartes empaqueté donné auquel la carte empaquetée donnée a été ajoutée,long remove(long pkCardSet, int pkCard)
, qui retourne l'ensemble de cartes empaqueté donné duquel la carte empaquetée donnée a été supprimée,boolean contains(long pkCardSet, int pkCard)
, qui retourne vrai ssi l'ensemble de cartes empaqueté donné contient la carte empaquetée donnée,long complement(long pkCardSet)
, qui retourne le complément de l'ensemble de cartes empaqueté donné,long union(long pkCardSet1, long pkCardSet2)
, qui retourne l'union des deux ensembles de cartes empaquetés donnés,long intersection(long pkCardSet1, long pkCardSet2)
, qui retourne l'intersection des deux ensembles de cartes empaquetés donnés,long difference(long pkCardSet1, long pkCardSet2)
, qui retourne la différence entre le premier ensemble de cartes empaqueté donné et le second, c-à-d l'ensemble des cartes qui se trouvent dans le premier ensemble mais pas dans le second,long subsetOfColor(long pkCardSet, Card.Color color)
, qui retourne le sous-ensemble de l'ensemble de cartes empaqueté donné constitué uniquement des cartes de la couleur donnée ; attention, tout commetrumpAbove
, cette méthode doit s'exécuter rapidement (voir les conseils de programmation ci-dessous),String toString(long pkCardSet)
, qui retourne la représentation textuelle de l'ensemble de cartes empaqueté donné, spécifiée plus bas.
2.1.1 Conseils de programmation
- Validation des arguments
Tout comme pour la classe
PackedScore
, nous vous conseillons fortement de valider les arguments des différentes méthodes au moyen d'assertions Java.Il faut noter à ce sujet que la méthode
add
(resp.remove
) ne produit aucune erreur si la carte qu'on lui passe appartient déjà (resp. n'appartient pas) à l'ensemble. Dans ce cas, l'ensemble retourné est simplement identique à celui donné en argument. - Méthodes
trumpAbove
etsubsetOfColor
Les méthodes
trumpAbove
etsubsetOfColor
doivent s'exécuter rapidement car elles sont utilisées par le joueur simulé pour décider quelle carte jouer.Dès lors, il vous est demander de les programmer sans utiliser de boucle. Pour ce faire, vous pouvez précalculer et stocker dans des attributs privés finaux de la classe différentes informations, qui pourraient p.ex. être :
- pour
trumpAbove
, un tableau (éventuellement bidimensionnel) contenant la valeur quetrumpAbove
doit retourner pour chacune des 36 cartes du jeu, - pour
subsetOfColor
, un tableau contenant, pour chaque couleur, un ensemble de toutes les cartes de cette couleur.
L'écriture de chacune des deux méthodes devient alors très simple, et elles s'exécutent rapidement.
- pour
- Méthodes
size
etget
La classe
Long
de la bibliothèque Java comporte plusieurs méthodes qui facilitent énormément l'écriture des méthodessize
etget
. Avant de se lancer dans leur programmation, il est donc fortement conseillé de lire et comprendre la documentation de :Les deux dernières en particulier peuvent être combinées judicieusement pour écrire la méthode
get
en trois lignes seulement — deux d'entre elles constituant une boucle. À vous de trouver comment ! - Méthodes
union
,intersection
, etc.
Il va sans dire que les opérateurs bit à bit de Java (
&
,|
,^
, etc.), ainsi que les opérateurs de décalage, permettent d'écrire la plupart des méthodes de la classePackedCardSet
de manière concise et efficace. Pensez donc à les utiliser ! - Méthode
toString
La méthode
toString
doit retourner une chaîne constituée de la représentation textuelle de chacune des cartes de l'ensemble, ordonnées par index croissant, séparées par une virgule et entourées d'accolades.Par exemple, l'extrait de programme suivant :
long s = PackedCardSet.EMPTY; int c1 = PackedCard.pack(Color.HEART, Rank.SIX); int c2 = PackedCard.pack(Color.SPADE, Rank.ACE); int c3 = PackedCard.pack(Color.SPADE, Rank.SIX); s = PackedCardSet.add(s, c1); s = PackedCardSet.add(s, c2); s = PackedCardSet.add(s, c3); System.out.println(PackedCardSet.toString(s));
doit imprimer ceci à l'écran (le symbole cœur pouvant aussi être plein) :
{♠6,♠A,♡6}
La manière la plus simple de construire cette chaîne consiste à utiliser la classe
StringJoiner
de la bibliothèque Java. L'extrait de code ci-dessous, dont vous pouvez vous inspirer, montre comment l'utiliser pour construire la chaîne{a,an,ane}
:StringJoiner j = new StringJoiner(",", "{", "}"); for (String s: new String[]{ "a", "an", "ane" }) j.add(s); System.out.println(j.toString()); // affiche {a,an,ane}
Pour plus de détails, reportez-vous à la documentation de la classe
StringJoiner
.
2.2 Classe CardSet
La classe CardSet
, du paquetage ch.epfl.javass.jass
, finale et immuable, représente un ensemble de cartes.
A l'image de PackedCardSet
, elle possède deux attributs publics, statiques et finaux, contenant des ensembles constants importants, à savoir :
CardSet EMPTY
, l'ensemble de cartes vide,CardSet ALL_CARDS
, l'ensemble des 36 cartes du jeu de Jass.
Comme les classes des étapes précédente, CardSet
ne possède pas de constructeur public, mais deux méthodes statiques jouant le rôle de constructeurs :
static CardSet of(List<Card> cards)
, qui retourne l'ensemble des cartes contenues dans la liste donnée,static CardSet ofPacked(long packed)
, qui retourne l'ensemble de cartes dontpacked
est la version empaquetée, ou lève l'exceptionIllegalArgumentException
si cet argument ne représente pas un ensemble empaqueté valide.
De plus, la classe CardSet
offre les méthodes publiques suivantes. Seule la première est décrite, les autres étant équivalentes à celles de PackedCardSet
portant le même nom :
long packed()
, qui retourne la version empaquetée de l'ensemble de cartes.boolean isEmpty()
,int size()
,Card get(int index)
,CardSet add(Card card)
,CardSet remove(Card card)
,boolean contains(Card card)
,CardSet complement()
,CardSet union(CardSet that)
,CardSet intersection(CardSet that)
,CardSet difference(CardSet that)
,CardSet subsetOfColor(Card.Color color)
.
Finalement, la classe CardSet
redéfinit les méthodes equals
, hashCode
et toString
de Object
:
equals
retourne vrai ssi l'objet qu'on lui passe est également un ensemble de cartes de typeCardSet
et qu'il contient exactement les mêmes cartes que le récepteur,hashCode
retourne simplement le résultat de la méthodehashCode
de la classeLong
appliquée à l'ensemble empaqueté,toString
se comporte comme la méthodetoString
dePackedCardSet
.
2.2.1 Conseils de programmation
- Immuabilité de
CardSet
Lors de la programmation de
CardSet
, souvenez-vous qu'il s'agit d'une classe immuable ! Cela implique que ses instances ne peuvent absolument pas être modifiées après création. - Liste de cartes (
List<Card>
)
Comme cela a été dit dans l'énoncé de l'étape 1, vous pouvez considérer pour l'instant que le type
List<Card>
est identique au typeArrayList<Card>
que vous connaissez déjà.Cela implique, entre autres, qu'il est possible de parcourir les éléments d'une telle liste au moyen de la boucle for each, comme illustré dans l'extrait de programme suivant :
List<Card> cards = …; for (Card c: cards) System.out.println(c);
2.3 Tests
Comme pour l'étape précédente, nous ne vous fournissons plus de tests mais un fichier de vérification de signatures contenu dans une archive Zip à importer dans votre projet.
3 Résumé
Pour cette étape, vous devez :
- écrire les classes
PackedCardSet
etCardSet
selon les instructions données plus haut, - tester votre code,
- documenter la totalité des entités publiques que vous avez définies,
- rendre votre code au plus tard le 8 mars 2019 à 16h30, via le système de rendu.
Ce rendu est un rendu testé, auquel 18 points sont attribués, au prorata des tests unitaires passés avec succès. Notez que la documentation de votre code ne sera pas évaluée avant le rendu intermédiaire. Dès lors, si vous êtes en retard, ne vous en préoccupez pas pour l'instant.
N'attendez surtout pas le dernier moment pour effectuer votre rendu, car vous n'êtes pas à l'abri d'imprévus. Souvenez-vous qu'aucun retard, aussi insignifiant soit-il, ne sera toléré !