Annonce au Jass
Série 5 – corrigé

Introduction

Le code du corrigé vous est fourni dans une archive Zip et les solutions aux différents exercices sont rapidement discutées ci-dessous.

Notez que pour des questions de présentation, les modificateurs private, public et static des différentes méthodes ont été omis ci-dessous. Ils figurent néanmoins bien entendu dans le code fourni.

Exercice 1 : classe Meld

Méthode addAllQuartetsInto

La version de addAllQuartetsInto basée sur une boucle consiste à parcourir la totalité des rangs à partir de 9 et à obtenir les cartes de ce rang pour chacune des couleurs.

Notez que nous avons choisi d'utiliser EnumSet ci-dessous étant donné que le type Card est un type énuméré, mais HashSet ou TreeSet auraient également convenu, les performances n'étant pas importantes ici.

void addAllQuartetsInto(List<Meld> melds) {
  List<Rank> ranksFrom9 =
    Rank.ALL.subList(Rank.NINE.ordinal(), Rank.COUNT);
  for (Rank rank: ranksFrom9) {
    Set<Card> quartet = EnumSet.noneOf(Card.class);
    for (Color color: Color.ALL)
      quartet.add(Card.ALL_OF.get(color).get(rank.ordinal()));
    melds.add(new Meld(quartet, quartetPoints(rank)));
  }
}

Méthode addAllSuitsInto

La méthode addAllSuitsInto s'écrit au moyen de 3 boucles imbriquées qui permettent respectivement de parcourir :

  1. les quatre couleurs,
  2. les différentes tailles de suites (3, 4 et 5),
  3. les différentes suites de la couleur et de la taille en question.

La dernière de ces boucles est la seule méritant quelques explications. Comme expliqué dans l'énoncé, l'idée consiste à avoir deux index, nommés ici i1 et i2, qui référencent respectivement la première carte et la dernière carte (en réalité, la position juste après la dernière carte) de la suite. Au moyen de ces deux index, il est facile d'extraire la sous-liste des cartes composant chacune des suites.

Notez que la troisième boucle utilise une caractéristique peu connue de Java, à savoir la possibilité de gérer plusieurs variables (ici i1 et i2) dans une seule boucle for.

void addAllSuitsInto(List<Meld> melds) {
  for (Color color: Color.ALL) {
    for (int size = 3; size <= 5; size += 1) {
      List<Card> cards = Card.ALL_OF.get(color);
      for (int i1 = 0, i2 = size;
	   i2 <= cards.size();
	   i1 += 1, i2 += 1) {
	Set<Card> suit = EnumSet.copyOf(cards.subList(i1,i2));
	melds.add(new Meld(suit, suitPoints(size)));
      }
    }
  }
}

Méthode allIn

La méthode allIn consiste simplement à parcourir la totalité des annonces et à déterminer lesquelles sont totalement incluses dans la main, ce qui se fait trivialement au moyen de la méthode containsAll.

List<Meld> allIn(Collection<Card> hand) {
  List<Meld> allIn = new ArrayList<>();
  for (Meld m: ALL) {
    if (hand.containsAll(m.cards()))
      allIn.add(m);
  }
  return allIn;
}

Exercice 2 : classe Sets

En utilisant la propriété mentionnée dans l'énoncé, à savoir que deux ensembles sont disjoints si et seulement si la cardinalité de leur union est égale à la somme de leurs cardinalités, il est relativement simple d'écrire la méthode mutuallyDisjoint.

L'idée est de calculer progressivement l'union de tous les ensembles (dans union) et la somme des cardinalités des ensembles (dans totalSize). Si à un instant donné la cardinalité de l'union devient inférieure à la somme des cardinalités, cela signifie que les ensembles ne sont pas disjoints deux à deux, et on retourne donc faux.

<T> boolean mutuallyDisjoint(Collection<Set<T>> sets) {
  Set<T> union = new HashSet<>();
  int totalSize = 0;
  for (Set<T> s: sets) {
    union.addAll(s);
    totalSize += s.size();
    if (union.size() < totalSize)
      return false;
  }
  return true;
}

Exercice 3 : classe MeldSet

La plus grosse partie de la définition de la classe MeldSet ne pose pas de problème particulier et est donc donnée ici sans commentaires additionnels. Les méthodes mutuallyDisjoint et allIn sont présentées séparément plus bas.

public final class MeldSet {
  private final Set<Meld> melds;

  // … méthode statique mutuallyDisjoint
  // … méthode statique allIn

  public static MeldSet of(Collection<Meld> melds) {
    if (! mutuallyDisjoint(melds))
      throw new IllegalArgumentException();
    return new MeldSet(melds);
  }

  private MeldSet(Collection<Meld> melds) {
    this.melds = unmodifiableSet(new HashSet<>(melds));
  }

  public int points() {
    int points = 0;
    for (Meld m: melds)
      points += m.points();
    return points;
  }

  @Override
  public String toString() {
    StringJoiner s = new StringJoiner(", ", "{", "}");
    for (Meld m: melds)
      s.add(m.cards().toString());
    return String.format("%3d: %s", points(), s);
  }
}

La méthode mutuallyDisjoint s'écrit très facilement en utilisant celle de Sets, il faut toutefois extraire les ensembles de cartes des annonces individuelles pour pouvoir l'appeler.

static boolean mutuallyDisjoint(Collection<Meld> melds) {
  List<Set<Card>> allSetsOfCards = new ArrayList<>();
  for (Meld m: melds)
    allSetsOfCards.add(m.cards());
  return Sets.mutuallyDisjoint(allSetsOfCards);
}

La méthode allIn est une retranscription en Java de la technique de calcul donnée dans l'énoncé :

  1. au moyen de allIn de Meld, on calcule l'ensemble des annonces individuelles présentes dans la main,
  2. au moyen de powerSet de Sets, on calcule l'ensemble des parties de cet ensemble d'annonces individuelles,
  3. au moyen de mutuallyDisjoint on filtre l'ensemble des parties pour ne garder que les ensembles d'annonces compatibles, et on construit les instances de MeldSet correspondantes.
static List<MeldSet> allIn(Collection<Card> hand) {
  List<MeldSet> r = new ArrayList<>();
  for (Set<Meld> melds: Sets.powerSet(Meld.allIn(hand))) {
    if (mutuallyDisjoint(melds))
      r.add(new MeldSet(melds));
  }
  return r;
}

Une fois la classe MeldSet terminée, on peut écrire un comparateur comparant deux instances via leurs points :

class MeldSetByPointsComparator
    implements Comparator<MeldSet> {
  @Override
  public int compare(MeldSet m1, MeldSet m2) {
    return Integer.compare(m1.points(), m2.points());
  }
}

et compléter le programme principal de la manière demandée dans l'énoncé :

System.out.printf("%nLes ensembles d'annonces […] :%n");
List<MeldSet> melds = MeldSet.allIn(hand);
melds.sort(new MeldSetByPointsComparator());
for (MeldSet m: melds)
  System.out.printf("  %s%n", m);