Annonce au Jass
Série 5

Introduction

Le but de cette série est d'utiliser les collections Java afin de déterminer les annonces présentes dans la main d'un joueur de Jass.

Notez que, dans un soucis de simplicité, nous avons décidé de ne pas gérer les annonces dans le cadre du projet Javass, et cette série constitue donc un bon complément à ce dernier.

Annonce au Jass

Au début d'un tour de Jass, les joueurs ont normalement la possibilité de déclarer qu'ils ont dans leur main certains sous-ensembles de cartes que l'on nomme annonces (meld en anglais) et qui peuvent leur rapporter des points. Par exemple, un joueur possédant le 6, 7 et 8 de pique dans sa main peut annoncer « 3 cartes » et, à supposer qu'aucun autre joueur n'ait d'annonce supérieure, cela ajoutera 20 points au score de son équipe.

Il existe deux catégories d'annonces au Jass :

  1. les suites composées de 3, 4 ou 5 cartes successives de la même couleur, p.ex. le 6, 7 et 8 de pique,
  2. les groupes de quatre cartes d'un même rang supérieur ou égal à 9, p.ex. les quatre as.

Ces différents types d'annonce rapportent un nombre de points variable, donnés par la table ci-dessous, dans laquelle figure aussi le nombre d'annonces différentes d'un type donné qui existent dans un jeu de 36 cartes.

Type Points Nombre
suite de 3 cartes d'une même couleur 20 28
suite de 4 cartes d'une même couleur 50 24
suite de 5 cartes d'une même couleur 100 20
quatre 10, dames, rois ou as 100 4
quatre 9 150 1
quatre valets 200 1
Total   78

Il est bien entendu possible qu'un main de Jass, composée de 9 cartes, contienne plusieurs annonces différentes, par exemple les quatre dames et les quatre as. Toutefois, une carte donnée ne peut participer qu'à une seule annonce.

Ainsi, une main contenant le 7, 8 et 9 de pique ainsi que le 9 de cœur, carreau et trèfles (et trois autres cartes quelconques) contient deux annonces :

  1. la suite composée du 7, 8 et 9 de pique, valant 20 points, et
  2. les quatre 9, valant 150 points.

Toutefois, les deux annonces ne peuvent pas être faites en même temps, car le 9 de pique est commun aux deux. Il faut donc choisir l'une des deux, et c'est logiquement l'annonce la plus forte qui est généralement choisie, ici les quatre 9.

Calcul de l'annonce

Notre but ici est de déterminer la totalité des annonces valides se trouvant dans une main de 9 cartes. Par exemple, la main :

[♠6, ♠7, ♠8, ♠9, ♡9, ♡K, ♢9, ♢A, ♣9]

contient les six annonces ci-dessous, données ici avec leur nombre de points total (à gauche) :

  0: {}
 20: {[♠6, ♠7, ♠8]}
 20: {[♠7, ♠8, ♠9]}
 50: {[♠6, ♠7, ♠8, ♠9]}
150: {[♠9, ♡9, ♢9, ♣9]}
170: {[♠9, ♡9, ♢9, ♣9], [♠6, ♠7, ♠8]}

Par convention, nous considérerons que l'annonce triviale constituée de 0 cartes (valant 0 points) fait toujours partie des annonces présentes dans une main. Dès lors, une main contient toujours au moins cette annonce triviale.

L'ensemble de toutes les annonces valides d'une main peut être calculé de manière relativement simple :

  1. on calcule l'ensemble des annonces individuelles présentes dans la main, sans se soucier des cartes communes,
  2. on calcule l'ensemble des parties de cet ensemble, c-à-d l'ensemble de tous ses sous-ensembles (voir plus bas),
  3. on supprime de cet ensemble tous les ensembles invalides, c-à-d dont au moins deux annonces partagent une carte.

Ainsi, en examinant la main donnée en exemple ci-dessus, on constate qu'elle contient 4 des 78 annonces individuelles possibles, à savoir :

1: [♠6, ♠7, ♠8]
2: [♠7, ♠8, ♠9]
3: [♠6, ♠7, ♠8, ♠9]
4: [♠9, ♡9, ♢9, ♣9]

L'ensemble des parties de cet ensemble de 4 annonces individuelles contient quant à lui 16 éléments (car 24 = 16), qui sont :

 1: {}
 2: {[♠6, ♠7, ♠8]}
 3: {[♠7, ♠8, ♠9]}
 4: {[♠6, ♠7, ♠8, ♠9]}
 5: {[♠9, ♡9, ♢9, ♣9]}
 6: {[♠6, ♠7, ♠8], [♠7, ♠8, ♠9]}
 7: {[♠6, ♠7, ♠8, ♠9], [♠6, ♠7, ♠8]}
 8: {[♠6, ♠7, ♠8, ♠9], [♠7, ♠8, ♠9]}
 9: {[♠9, ♡9, ♢9, ♣9], [♠6, ♠7, ♠8]}
10: {[♠9, ♡9, ♢9, ♣9], [♠7, ♠8, ♠9]}
11: {[♠9, ♡9, ♢9, ♣9], [♠6, ♠7, ♠8, ♠9]}
12: {[♠6, ♠7, ♠8, ♠9], [♠6, ♠7, ♠8], [♠7, ♠8, ♠9]}
13: {[♠9, ♡9, ♢9, ♣9], [♠6, ♠7, ♠8], [♠7, ♠8, ♠9]}
14: {[♠9, ♡9, ♢9, ♣9], [♠6, ♠7, ♠8, ♠9], [♠6, ♠7, ♠8]}
15: {[♠9, ♡9, ♢9, ♣9], [♠6, ♠7, ♠8, ♠9], [♠7, ♠8, ♠9]}
16: {[♠9, ♡9, ♢9, ♣9], [♠6, ♠7, ♠8, ♠9], [♠6, ♠7, ♠8], …}

(Le dernier ensemble a été tronqué pour des raisons de présentation.)

En consultant ces 16 ensembles, on constate que seuls 6 d'entre eux sont valides car les annonces qu'ils contiennent n'ont aucune carte en commun. Il s'agit de ceux portant les numéros 1, 2, 3, 4, 5 et 9, les mêmes que ceux donnés plus haut.

Squelette

Pour vous permettre de débuter cette série, nous mettons à votre disposition une archive Zip contenant les définitions parfois partielles des types suivants :

  • Card, un type énuméré représentant les 36 cartes d'un jeu de Jass,
  • Meld, une classe représentant une annonce simple, à compléter dans le cadre de l'exercice 1,
  • Sets, une classe non instanciable contenant des méthodes utilitaires travaillant sur les ensembles, à compléter dans le cadre de l'exercice 2,
  • Example, une classe principale d'exemple utilisant les classes de cette série pour déterminer les annonces présentes dans la main d'exemple donnée ci-dessus, à terminer dans le cadre de l'exercice 3.

Avant d'aller plus loin, lisez le code de l'énumération Card. Notez en particulier l'attribut ALL_OF qui associe à chaque couleur la liste des cartes de cette couleur, ordonnées par rang croissant. Cet attribut vous sera utile à plusieurs reprises.

Exercice 1 : classe Meld

La classe Meld, immuable, représente une annonce simple. Son attribut ALL contient les 78 annonces individuelles existant dans un jeu de Jass complet. Ces annonces sont calculées par deux méthodes dont la rédaction constitue le but de cet exercice.

Méthode addAllQuartetsInto

Écrivez le corps de la méthode addAllQuartetsInto, dont le but est d'ajouter à la liste d'annonces qu'elle reçoit toutes celles formées de 4 cartes de même rang, que nous nommerons (en anglais) quartet.

Vous pouvez écrire cette méthode soit de manière triviale, en ajoutant les annonces une à une dans la liste passée en argument (il n'y en a que 6), ou de manière plus propre en utilisant une boucle. Utilisez la méthode quartetPoints pour connaître les points de chacune de ces annonces.

Une fois votre méthode terminée, exécutez le programme principal de la classe Example. Vous devriez voir vos 6 annonces s'afficher.

Méthode addAllSuitsInto

Écrivez le corps de la méthode addAllSuitsInto, dont le but est d'ajouter à la liste d'annonce qu'elle reçoit toutes celles formées de suites de 3, 4 ou 5 cartes.

Notez que cette méthode peut être écrite de manière concise et claire en utilisant deux index qui parcourent en parallèle la liste des cartes d'une couleur donnée, et qui sont séparés par une distance égale à la taille de la suite à créer. Ces index peuvent être passés à la méthode subList de List pour extraire les cartes de la suite.

Une fois votre méthode terminée, exécutez à nouveau le programme principal de la classe Example. Vous devriez voir la totalité des 78 annonces possibles s'afficher.

Méthode allIn

Écrivez le corps de la méthode allIn, qui retourne la totalité des annonces individuelles se trouvant dans la main donnée en argument. Il s'agit simplement du sous-ensemble des 78 annonces possibles dont toutes les cartes figurent dans la main. La méthode containsAll de Collection peut vous être utile pour le calculer.

Une fois votre méthode terminée, exécutez encore une fois le programme principal de la classe Example. Vous devriez voir les quatre annonces contenues dans la main d'exemple s'afficher.

Exercice 2 : classe Sets

La classe Sets contient des méthodes utilitaires statiques travaillant sur les ensembles. Actuellement, elle contient déjà la méthode powerSet qui retourne l'ensemble des parties de l'ensemble qu'on lui donne en argument.

L'ensemble des parties d'un ensemble \(E\), noté \({\cal P}(E)\), est l'ensemble de tous ses sous-ensembles. Par exemple, l'ensemble des parties de l'ensemble \(E=\{1,2,3\}\) est : \[ {\cal P}(\{1,2,3\}) = \{ ∅, \{1\}, \{2\}, \{3\}, \{1,2\}, \{1,3\}, \{2,3\}, \{1,2,3\} \} \]

Comme cet exemple l'illustre, la cardinalité de l'ensemble \(E\) et de l'ensemble de ses parties \({\cal P}(E)\) sont liées par la relation suivante : \[ |{\cal P}(E)| = 2^{|E|} \]

L'ensemble des parties peut se calculer au moyen d'un algorithme récursif très élégant. Cette élégance n'est malheureusement pas apparente dans sa mise en œuvre Java, en raison de limitations du langage et de sa bibliothèque. Il ne vous est donc pas demandé de comprendre la méthode powerSet avant de continuer, mais savoir ce qu'elle fait sera indispensable à l'exercice suivant.

Avant de vous atteler à l'exercice 3, il vous faut néanmoins écrire le corps de la méthode mutuallyDisjoint qui retourne vrai si et seulement si les ensembles de la collection donnée sont disjoints deux à deux. Pour ce faire, souvenez-vous d'une part que deux ensembles \(E_1\) et \(E_2\) sont disjoints si et seulement si : \[ |E_1\cup E_2| = |E_1| + |E_2| \] et d'autre part que la méthode addAll de l'interface Collection permet d'ajouter tous les éléments d'un ensemble à un autre, c-à-d d'en faire l'union.

Exercice 3 : classe MeldSet

La classe MeldSet, à écrire dans le cadre de cet exercice, représente un ensemble d'annonces compatibles, c-à-d dont les ensembles de cartes sont disjoints deux à deux.

Cette classe, publique, finale et immuable, offre trois méthodes statiques :

  • boolean mutuallyDisjoint(Collection<Meld> melds), qui retourne vrai ssi les ensembles de cartes des annonces données sont disjoints deux à deux,
  • List<MeldSet> allIn(Collection<Card> hand), qui retourne tous les ensembles d'annonces compatibles présents dans la main donnée,
  • MeldSet of(Collection<Meld> melds), qui retourne l'ensemble des annonces données, ou lève IllegalArgumentException si ces annonces ne sont pas compatibles, c-à-d si leurs ensembles de cartes ne sont pas disjoints deux à deux.

MeldSet ne possède aucun constructeur public, la méthode of ci-dessus en jouant le rôle. Par contre, elle offre la méthode publique suivante :

  • int points(), qui retourne le nombre de points total de l'ensemble d'annonces.

Écrivez la classe MeldSet, en vous aidant bien entendu des méthodes disponibles dans les classes Meld et Sets. Une fois que vous l'avez terminée, complétez la méthode main de la classe Example afin de :

  1. calculer la totalité des 6 annonces valides présentes dans la main d'exemple,
  2. les trier en fonction de leur nombre de points total, ce qui implique d'écrire un comparateur à passer à la méthode sort de List,
  3. les afficher, dans cet ordre, à l'écran.