Partitions et messages

ChaCuN – étape 4

1. Introduction

Cette quatrième étape a deux buts : premièrement, écrire une classe regroupant les différentes partitions de zones utilisées lors d'une partie ; et deuxièmement, écrire une classe permettant de garder trace des événements importants — principalement les gains de points — se produisant lors d'une partie.

2. Concepts

2.1. Partitions de zones

Comme nous l'avons vu, un total de quatre partitions de zones existent dans ChaCuN : celle des forêts, celle des prés, celle des rivières et celle des zones aquatiques — rivières et lacs. Ces partitions évoluent au cours de la partie, au fur et à mesure que des tuiles sont déposées sur le plateau, connectant certaines aires entre elles.

2.1.1. Contenu exact

Une question qui se pose au sujet de ces partitions est celle de leur contenu exact : s'agit-il de partitions de toutes les zones des tuiles du jeu — non seulement celles déjà posées, mais aussi celles qui ne le sont pas encore — ou s'agit-il de partitions des zones des tuiles déjà posées ?

Si ces partitions contiennent toujours les zones de toutes les tuiles (posées ou non), alors au début d'une partie elles doivent être initialisées avec les aires de toutes les tuiles du jeu. Ensuite, au cours de la partie, lorsqu'une tuile est déposée et que certaines aires sont connectées entre elles, les partitions changent — dans la mesure où les aires qui les constituent évoluent — mais le nombre total de zones dans chaque partition ne change pas. La seule chose qui change est leur répartition dans les différentes aires.

Si les partitions ne contiennent que les zones des tuiles posées, alors au tout début d'une partie — avant même que la tuile de départ n'ait été placée sur le plateau — toutes les partitions sont vides. Au cours de la partie, lorsqu'une nouvelle tuile est déposée, ses aires sont d'abord ajoutées aux différentes partitions, puis certaines de ces aires sont connectées avec celles des tuiles voisines.

Pour ce projet, nous avons décidé d'utiliser la seconde solution, dans laquelle les différentes partitions ne contiennent que les zones des tuiles déjà posées. Cela réduit leur taille et peut faciliter le débogage.

2.1.2. Ajout d'une tuile

Le fait que les partitions ne contiennent que les zones des tuiles déjà posées sur le plateau implique que, au moment où une tuile est déposée, les aires formées par ses zones doivent être ajoutées aux différentes partitions.

Par exemple, avant le début d'une partie, toutes les partitions sont vides, et au moment où la tuile de départ est posée, ses aires sont ajoutées aux différentes partitions. Pour mémoire, la tuile de départ comporte 5 zones visibles sur l'image ci-dessous.

zones_56;128.png
Figure 1 : La tuile de départ (identifiant 56) découpée en zones

Au moment où elle est posée sur le plateau, les quatre partitions — qui sont vides à ce stade — doivent être augmentées ainsi :

  • celle des prés est augmentée de deux nouvelles aires, chacune composée d'une seule zone, qui sont respectivement les zones 0 (identifiant 560) et 2 (identifiant 562) de la tuile,
  • celle des forêts est augmentée d'une unique nouvelle aire, composée de la zone 1 (identifiant 561) de cette tuile,
  • celle des rivières est augmentée d'une unique nouvelle aire, composée de la zone 3 (identifiant 563) de cette tuile,
  • celle des zones aquatiques — les réseaux hydrographiques — est augmentée d'une unique nouvelle aire, composée des zones 3 (la rivière, identifiant 563) et 8 (le lac, identifiant 568) de cette tuile.

Ces partitions peuvent s'écrire de manière compacte en représentant chaque zone par son identifiant, et chaque partition par un ensemble de sous-ensembles de ces identifiants :

  • la partition des prés est {{560}, {562}},
  • la partition des forêts est {{561}},
  • la partition des rivières est {{563}},
  • la partition des zones aquatiques est {{563, 568}}.

Comme nous l'avons vu à l'étape précédente, chaque aire possède un certain nombre de connexions ouvertes. Dès lors, lorsqu'une tuile est posée, le nombre de connexions ouvertes de chacune de ses aires doit être déterminé. Cela peut se faire en comptant, pour chaque aire, le nombre de fois qu'une de ses zones apparaît sur l'un des bords de la tuile.

2.1.3. Connexion des aires de deux tuiles

Lorsque deux tuiles sont placées côte à côte sur le plateau, les aires de leur côté commun doivent être connectées entre elles. Lorsque ce bord commun est un pré ou une forêt, cela ne pose pas de problème particulier ; lorsqu'il s'agit d'une rivière, il faut prendre garde à connecter les bonnes aires entre elles.

Par exemple, admettons que l'on dépose la tuile 17 à l'ouest de la tuile de départ, pour obtenir le plateau ci-dessous.

board_04-two-tiles-river.png
Figure 2 : Un plateau constitué des tuiles 17 et 56

En regardant cette image, on voit que le pré 2 de la tuile 17 — qui contient le cerf — doit être connecté avec le pré 0 de la tuile 56 — qui contient l'auroch. De même, le pré 0 de la tuile 17 — vide — doit être connecté avec le pré 2 — vide lui aussi — de la tuile 56. Or il faut se souvenir que les prés d'un bord rivière sont toujours donnés dans l'ordre dans lequel on les rencontre lors d'un parcours du pourtour de la tuile en sens horaire. Cela implique que les deux bords connectés dans l'image ci-dessus sont :

  • pour la tuile 17, le bord rivière constitué, dans l'ordre, du pré 2, de la rivière 1, puis du pré 0,
  • pour la tuile 56, le bord rivière constitué, dans l'ordre, du pré 2, de la rivière 3, puis du pré 0.

En d'autres termes, le premier pré du premier bord doit être connecté avec le second du second bord, et inversement.

Une fois ces connexions effectuées, on obtient les partitions suivantes :

  • la partition des prés est {{170,562}, {172,560}, {174}},
  • la partition des forêts est {{561}},
  • la partition des rivières est {{171,563}, {173}},
  • la partition des zones aquatiques est {{171,563,568}, {173}}.

2.2. Messages

Comme cela a été brièvement expliqué dans l'introduction au projet, lors d'une partie de ChaCuN, des messages sont affichés sur la droite de l'interface, sur ce que nous nommerons le tableau d'affichage (message board). Le but de ces messages est d'informer les joueurs du déroulement de la partie, généralement pour leur dire qui a gagné des points, et pourquoi.

La partie graphique du tableau d'affichage sera mise en œuvre dans une étape ultérieure, mais la classe permettant de générer et de stocker les messages le sera dans celle-ci. Les sections suivantes énumèrent donc les différentes situations dans lesquelles un message doit être généré.

2.2.1. Fermeture d'une forêt avec menhir

Lorsqu'un joueur utilise une tuile normale pour fermer une forêt, et que celle-ci contient au moins un menhir, il a le droit de jouer un second tour en plaçant une tuile menhir — à supposer qu'il en reste. Dans ce cas, un message est affiché sur le tableau d'affichage.

2.2.2. Fermeture d'une forêt ou d'une rivière

Lorsqu'une forêt ou une rivière est fermée, et qu'elle est occupée par au moins un pion (cueilleur ou pêcheur), ses occupants majoritaires remportent des points. Un message est alors affiché sur le tableau d'affichage.

2.2.3. Pose de la tuile contenant la fosse à pieu

Lorsqu'un joueur pose la tuile contenant la fosse à pieu, il remporte les points correspondant aux éventuels animaux qui se trouvent dans le même pré que la fosse, et sur l'une des 8 tuiles voisines : les 4 situées directement au nord, à l'est, au sud et à l'ouest de celle contenant la fosse, ainsi que les 4 situées dans les diagonales — au nord-est, au sud-est, au sud-ouest et au nord-ouest. Si le nombre de points en question est supérieur à 0, un message est affiché sur le tableau d'affichage.

Dans tous les cas, les animaux se trouvant dans le même pré que la fosse et sur l'une des 8 tuiles voisines sont ensuite annulés, c.-à-d. qu'ils sont ignorés pour le reste de la partie.

2.2.4. Pose de la tuile contenant la pirogue

Lorsqu'un joueur pose la tuile contenant la pirogue, il remporte un nombre de points dépendant du nombre de lacs se trouvant dans le même réseau hydrographique qu'elle. Ce nombre de points est forcément supérieur à 0, car la pirogue elle-même se trouve dans un lac. Un message est donc affiché sur le tableau d'affichage.

2.2.5. Fin de partie

À la fin de la partie, les occupants majoritaires des prés et des réseaux hydrographiques gagnent des points dépendant respectivement des animaux présents — et non annulés — dans le pré, et des poissons nageant dans le réseau hydrographique. Pour chaque pré ou réseau hydrographique occupé et rapportant un nombre de points non nul, un message est affiché sur le tableau d'affichage.

Si la tuile contenant la grande fosse à pieux a été posée, et que le pré dans lequel elle se trouve est occupé, alors ses occupants majoritaires reçoivent des points additionnels pour les éventuels animaux se trouvant dans le même pré que la fosse et sur l'une des 8 tuiles voisines. Si ce nombre de points est supérieur à 0, un message est affiché sur le tableau d'affichage.

Si la tuile contenant le radeau a été posée, et que le réseau hydrographique contenant le radeau est occupé, alors ses occupants majoritaires reçoivent des points additionnels, et un message est affiché sur le tableau d'affichage.

Finalement, lorsque tous les points ont été comptés, un dernier message nommant le ou les joueur(s) ayant remporté la partie, et les points qu'ils ont obtenus, est affiché sur le tableau d'affichage. (En cas d'égalité de points, les joueurs en ayant le plus se partagent la victoire.)

3. Mise en œuvre Java

3.1. Enregistrement ZonePartitions

L'enregistrement ZonePartitions (avec un s à la fin !) du paquetage principal, public et immuable, regroupe les quatre partitions de zones du jeu. Il possède donc les attributs suivants :

  • ZonePartition<Zone.Forest> forests, la partition des forêts,
  • ZonePartition<Zone.Meadow> meadows, la partition des prés,
  • ZonePartition<Zone.River> rivers, la partition des rivières,
  • ZonePartition<Zone.Water> riverSystems, la partition des zones aquatiques (rivières et lacs).

En dehors des méthodes ajoutées automatiquement par Java, ZonePartitions ne possède rien d'autre que l'attribut public, final et statique suivant :

  • ZonePartitions EMPTY, qui représente un groupe de 4 partitions vides.

3.2. Classe ZonePartitions.Builder

La classe Builder, publique, finale et imbriquée statiquement dans ZonePartitions, sert de bâtisseur à la classe ZonePartitions. Logiquement, ses attributs (privés) sont quatre bâtisseurs de partitions de zones, de type ZonePartition.Builder<…>.

Builder offre un unique constructeur public :

  • Builder(ZonePartitions initial), qui retourne un nouveau bâtisseur dont les quatre partitions sont initialement identiques à celles du groupe de quatre partitions donné.

En plus de ce constructeur, Builder offre les méthodes publiques ci-dessous. Avant de commencer à les programmer, il est fortement recommandé de lire les conseils de programmation plus bas.

  • void addTile(Tile tile), qui ajoute aux quatre partitions les aires correspondant aux zones de la tuile donnée,
  • void connectSides(TileSide s1, TileSide s2), qui connecte les deux bords de tuiles donnés, en connectant entre elles les aires correspondantes, ou lève IllegalArgumentException si les deux bords ne sont pas de la même sorte,
  • void addInitialOccupant(PlayerColor player, Occupant.Kind occupantKind, Zone occupiedZone), qui ajoute un occupant initial, de la sorte donnée et appartenant au joueur donné, à l'aire contenant la zone donnée, ou lève IllegalArgumentException si la sorte d'occupant donnée ne peut pas occuper une zone de la sorte donnée,
  • void removePawn(PlayerColor player, Zone occupiedZone), qui supprime un occupant — un pion — appartenant au joueur donné de l'aire contenant la zone donnée, ou lève IllegalArgumentException si la zone est un lac,
  • void clearGatherers(Area<Zone.Forest> forest), qui supprime tous les occupants — des pions jouant le rôle de cueilleurs — de la forêt donnée,
  • void clearFishers(Area<Zone.River> river), qui supprime tous les occupants — des pions jouant le rôle de pêcheurs — de la rivière donnée,
  • ZonePartitions build(), qui retourne le groupe de quatre partitions en cours de construction.

Notez que removePawn est destinée à être utilisée pour mettre en œuvre le pouvoir spécial du chaman, qui permet au joueur qui le pose de récupérer, s'il le désire, l'un de ses pions. C'est la raison pour laquelle removePawn ne peut que supprimer un pion, et pas une hutte, contrairement à addInitialOccupant qui permet d'ajouter aussi bien un pion qu'une hutte.

3.2.1. Conseils de programmation

  1. Méthode addTile

    La méthode addTile est destinée à être utilisée lorsqu'une nouvelle tuile est posée sur le plateau, afin d'ajouter ses aires aux différentes partitions. Il faut noter que addTile se charge uniquement d'ajouter les aires de la tuile, et pas de les connecter avec celles de tuiles voisines — que addTile ne peut de toute manière pas connaître. Néanmoins, la méthode addTile n'est pas totalement triviale à écrire, et nous vous conseillons donc une manière de procéder.

    Dans un premier temps, déterminez le nombre de connexions ouvertes de chaque zone, que vous stockez dans un tableau indexé par le numéro local de la zone — compris entre 0 et 9. Lorsqu'une rivière est attachée à un lac, considérez pour l'instant que la connexion entre les deux est ouverte.

    Par exemple, pour la tuile de départ, faites comme si la rivière n'était pas connectée au lac, ce qui implique que le nombre de connexions ouvertes de chaque zone est :

    • 2 pour les zones 0 (pré à l'auroch), 1 (forêt) et 3 (rivière),
    • 1 pour les zones 2 (pré vide) et 8 (lac).

    Dans un second temps, ajoutez toutes les zones aux différentes partitions, en tant que nouvelles aires inoccupées et constituées uniquement de la zone en question, avec le bon nombre de connexions ouvertes. Notez bien que le nombre de connexions ouvertes calculé précédemment est correct dans tous les cas sauf un : lorsqu'une rivière est connectée à un lac, elle forme une aire rivière avec une connexion ouverte de moins que le nombre calculé, car le lac termine la rivière — mais pas le réseau hydrographique qui les contient les deux !

    Ainsi, lors de l'ajout de la tuile de départ, les aires sont à ce stade celles ci-dessous, le nombre entre crochets attaché à chacune d'entre elles donnant son nombre de connexions ouvertes :

    • la partition des prés est {{560}[2], {562}[1]},
    • la partition des forêts est {{561}[2]},
    • la partition des rivières est {{563}[1]},
    • la partition des zones aquatiques est {{563}[2], {568}[1]}.

    Finalement, parcourez une dernière fois les rivières attachées à un lac et, dans la partition des aires aquatiques, connectez leur aire avec celle du lac.

    Ainsi, lors de l'ajout de la tuile de départ, les aires finalement calculées sont les mêmes que ci-dessus, sauf celle des zones aquatiques, qui est maintenant {{563,568}[1]}, ce qui est correct.

  2. Méthode connectSides

    La méthode connectSides a la caractéristique de devoir se comporter différemment en fonction du type exact de ses arguments. Par exemple, si elle est appelée avec deux bords pré, alors elle doit modifier la partition des prés ; si elle est appelée avec deux bords forêt, elle doit modifier la partition des forêts ; et ainsi de suite.

    Pour écrire le code correspondant, une solution serait de faire une séquence de tests avec instanceof, quelque chose comme :

    public void connectSides(TileSide s1, TileSide s2) {
      if (s1 instanceof TileSide.Meadow ms1) {
        Zone.Meadow m1 = ms1.meadow();
        if (s2 instanceof TileSide.Meadow ms2) {
          Zone.Meadow m2 = ms2.meadow();
          // … connecte m1 et m2
        } else {
          // … lève une exception
        }
      }
      // … autres cas
    }
    

    Ce genre de code est toutefois fastidieux à écrire, et peu élégant. Heureusement, depuis la version 21 de Java, il est possible de l'améliorer nettement grâce au filtrage de motifs (pattern matching), dont nous avons déjà vu une variante à l'étape 2 — celle liée à instanceof, qui est d'ailleurs utilisée dans le code ci-dessus.

    La première amélioration qu'il est possible d'apporter au code ci-dessus consiste à utiliser les motifs d'enregistrements (record patterns), qui permettent d'extraire dans des variables les composantes d'un enregistrement, en même temps que l'on teste son type exact. La version ci-dessous du code utilise cela pour directement nommer les zones pré m1 et m2 dans le contexte du test instanceof :

    public void connectSides(TileSide s1, TileSide s2) {
      if (s1 instanceof TileSide.Meadow(Zone.Meadow m1)) {
        if (s2 instanceof TileSide.Meadow(Zone.Meadow m2)) {
          // … connecte m1 et m2
        } else {
          // … lève une exception
        }
      }
      // … autres cas
    }
    

    La seconde amélioration que l'on peut apporter à ce code consiste à utiliser le filtrage de motif dans un switch plutôt que de faire une séquence de tests instanceof. On obtient alors le code suivant :

    public void connectSides4(TileSide s1, TileSide s2) {
      switch (s1) {
        case TileSide.Meadow(Zone.Meadow m1) -> {
          if (s2 instanceof TileSide.Meadow(Zone.Meadow m2)) {
            // … connecte m1 et m2
          } else {
            // … lève une exception
          }
        }
        // … autres cas
      }
    }
    

    Finalement, on peut encore apporter une troisième amélioration à ce code en tirant parti du fait qu'il est possible d'attacher, au moyen du mot-clef when, une condition à un cas d'un tel switch. La condition doit être vraie pour que le code correspondant au cas soit exécuté. On obtient alors le code suivant :

    public void connectSides(TileSide s1, TileSide s2) {
      switch (s1) {
        case TileSide.Meadow(Zone.Meadow m1)
         when s2 instanceof TileSide.Meadow(Zone.Meadow m2) ->
          // … connecte m1 et m2
        // … autres cas
        default ->
          // … lève une exception
      }
    }
    

    Le filtrage de motifs est un concept extrêmement puissant, développé à l'origine dans le contexte des langages de programmation fonctionnels, mais qui s'est depuis popularisé et a été ajouté à la plupart des langages modernes. Il vaut donc la peine de se familiariser avec lui, et les personnes intéressées trouveront plus de détails à son sujet dans la §6 du guide des améliorations apportées à Java 21.

  3. Méthodes addInitialOccupant et removePawn

    Tout comme connectSides, addInitialOccupant et removePawn doivent se comporter différemment en fonction du type exact de la zone qu'on leur passe. S'il s'agit d'une zone forêt, alors elles doivent ajouter/enlever un pion (cueilleur) de l'aire forêt qui contient la zone ; s'il s'agit d'une zone pré, alors elles doivent ajouter/enlever un pion (chasseur) de l'aire pré qui contient la zone ; et ainsi de suite. Là aussi, un switch qui distingue ces différents cas au moyen du filtrage de motifs permet d'écrire le code de manière concise et élégante.

3.3. Interface TextMaker (fournie)

Pour faciliter votre travail, nous mettons à votre disposition l'interface TextMaker, publique, qui appartient au paquetage principal et est destinée à être implémentée par des classes capables de générer tout le texte qui apparaît dans l'interface graphique de ChaCuN — principalement le texte des messages, mais aussi celui donnant le nombre de points de chaque joueur, leur nom, etc.

Cette interface vous est fournie dans une archive Zip qu'il vous faut télécharger avant d'en extraire le contenu. Cela fait, le plus simple pour la placer dans votre projet consiste à trouver le fichier TextMaker.java qu'elle contient puis de le glisser dans la zone Project de votre projet IntelliJ, dans le paquetage ch.epfl.chacun.

Après avoir importé cette interface, lisez les commentaires attachés à ses méthodes, dont vous aurez besoin pour créer les messages du tableau d'affichage, comme expliqué à la §3.5 plus bas.

Notez que certaines méthodes de TextMaker prennent en argument une table associative de type Map<…>. Si vous êtes en avance dans le projet, il est possible que ce type de collection n'ait pas encore été vu au cours. Dans ce cas, nous vous conseillons d'ignorer les méthodes en question dans un premier temps, et d'y revenir une fois la matière examinée au cours.

3.4. Enregistrement MessageBoard.Message

L'enregistrement Message, imbriqué dans l'enregistrement MessageBoard décrit à la section suivante, public et immuable, représente un message affiché sur le tableau d'affichage. Il possède les attributs suivants :

  • String text, le texte du message,
  • int points, les points associés au message — qui peuvent valoir 0, par exemple si le message ne signale pas un gain de points,
  • Set<PlayerColor> scorers, l'ensemble des joueurs ayant remportés les points, qui peut être vide si le message ne signale pas un gain de points,
  • Set<Integer> tileIds, les identifiants des tuiles concernées par le message, ou un ensemble vide si le message ne concerne pas un ensemble de tuiles.

Les trois derniers attributs n'ont généralement de sens que pour les messages qui décrivent un gain de points, mais cela constitue la grande majorité d'entre eux. Par « tuiles concernées par le message », on entend généralement les tuiles qui ont permis au(x) joueur(s) de gagner les points.

En dehors de ces attributs et des méthodes ajoutées automatiquement par Java aux enregistrements, Message ne possède qu'un constructeur compact qui vérifie que le texte passé n'est pas null, que les points ne sont pas inférieurs à 0, et copie les deux ensembles pour garantir l'immuabilité.

3.5. Enregistrement MessageBoard

L'enregistrement MessageBoard du paquetage principal, public et immuable, représente le contenu du tableau d'affichage. Il possède les attributs suivants :

  • TextMaker textMaker, l'objet permettant d'obtenir le texte des différents messages,
  • List<Message> messages, la liste des messages affichés sur le tableau, du plus ancien au plus récent.

Le constructeur compact de MessageBoard garantit l'immuabilité de la classe. En dehors de ce constructeur, MessageBoard offre les méthodes publiques suivantes :

  • Map<PlayerColor, Integer> points(), qui retourne une table associant à tous les joueurs figurant dans les gagnants (scorers) d'au moins un message, le nombre total de points obtenus,
  • MessageBoard withScoredForest(Area<Zone.Forest> forest), qui retourne un tableau d'affichage identique au récepteur, sauf si la forêt donnée est occupée, auquel cas le tableau contient un nouveau message signalant que ses occupants majoritaires ont remporté les points associés à sa fermeture,
  • MessageBoard withClosedForestWithMenhir(PlayerColor player, Area<Zone.Forest> forest), qui retourne un tableau d'affichage identique au récepteur, mais avec un nouveau message signalant que le joueur donné a le droit de jouer un second tour après avoir fermé la forêt donnée, car elle contient un ou plusieurs menhirs,
  • MessageBoard withScoredRiver(Area<Zone.River> river), qui retourne un tableau d'affichage identique au récepteur, sauf si la rivière donnée est occupée, auquel cas le tableau contient un nouveau message signalant que ses occupants majoritaires ont remporté les points associés à sa fermeture,
  • MessageBoard withScoredHuntingTrap(PlayerColor scorer, Area<Zone.Meadow> adjacentMeadow), qui retourne un tableau d'affichage identique au récepteur, sauf si la pose de la fosse à pieux a permis au joueur donné, qui l'a posée, de remporter des points, auquel cas le tableau contient un nouveau message signalant cela — le pré donné comportant les mêmes occupants que le pré contenant la fosse, mais uniquement les zones se trouvant à sa portée,
  • MessageBoard withScoredLogboat(PlayerColor scorer, Area<Zone.Water> riverSystem), qui retourne un tableau d'affichage identique au récepteur, mais avec un nouveau message signalant que le joueur donné a obtenu les points correspondants à la pose de la pirogue dans le réseau hydrographique donné,
  • MessageBoard withScoredMeadow(Area<Zone.Meadow> meadow, Set<Animal> cancelledAnimals), qui retourne un tableau d'affichage identique au récepteur, sauf si le pré donné est occupé et que les points qu'il rapporte à ses occupants majoritaires — calculés en faisant comme si les animaux annulés donnés n'existaient pas — sont supérieurs à 0, auquel cas le tableau contient un nouveau message signalant que ces joueurs-là ont remporté les points en question,
  • MessageBoard withScoredRiverSystem(Area<Zone.Water> riverSystem), qui retourne un tableau d'affichage identique au récepteur, sauf si le réseau hydrographique donné est occupé et que les points qu'il rapporte à ses occupants majoritaires sont supérieurs à 0, auquel cas le tableau contient un nouveau message signalant que ces joueurs-là ont remporté les points en question,
  • MessageBoard withScoredPitTrap(Area<Zone.Meadow> adjacentMeadow, Set<Animal> cancelledAnimals), qui retourne un tableau d'affichage identique au récepteur, sauf si le pré donné, qui contient la grande fosse à pieux, est occupé et que les points — calculés en faisant comme si les animaux annulés donnés n'existaient pas — qu'il rapporte à ses occupants majoritaires sont supérieurs à 0, auquel cas le tableau contient un nouveau message signalant que ces joueurs-là ont remporté les points en question ; comme pour la « petite » fosse à pieux, le pré donné comporte les mêmes occupants que le pré contenant la fosse, mais uniquement les zones se trouvant à sa portée,
  • MessageBoard withScoredRaft(Area<Zone.Water> riverSystem), qui retourne un tableau d'affichage identique au récepteur, sauf si le réseau hydrographique donné, qui contient le radeau, est occupé, auquel cas le tableau contient un nouveau message signalant que ses occupants majoritaires ont remporté les points correspondants,
  • MessageBoard withWinners(Set<PlayerColor> winners, int points), qui retourne un tableau d'affichage identique au récepteur, mais avec un nouveau message signalant que le(s) joueur(s) donné(s) a/ont remporté la partie avec le nombre de points donnés.

3.5.1. Conseils de programmation

Les méthodes withScoredHuntingTrap, withScoredMeadow et withScoredPitTrap doivent toutes les trois déterminer quels sont les animaux présents dans le pré qu'on leur a donné, en prenant garde à exclure les éventuels animaux annulés. Cela peut se faire aisément au moyen d'une méthode de Area.

Une fois ces animaux déterminés, le nombre de mammouths, aurochs et cerfs peut être passé à une méthode de Points pour déterminer les points qu'ils rapportent.

Finalement, la totalité des animaux non annulés peut être passée à une méthode de TextMaker pour obtenir le texte du message à ajouter au tableau.

3.6. Tests

Comme pour l'étape précédente, nous ne vous fournissons plus de tests mais un fichier de vérification de signatures à importer dans votre projet.

Notez que, si nous vous avons fourni l'interface TextMaker, nous ne vous avons fourni aucune mise en œuvre de cette interface. Pour pouvoir tester la classe MessageBoard, il vous faudra donc, dans vos tests, créer une classe implémentant cette interface. Nous vous conseillons de rendre cette classe aussi simple que possible, et en particulier de ne pas essayer de générer des messages grammaticalement corrects à ce stade, car vous ferez cela dans une étape ultérieure. Une classe dont toutes les méthodes retournent une simple concaténation de la représentation textuelle des arguments suffit largement aux tests. Par exemple, la méthode playersScoredForest ci-dessous fait l'affaire :

String playersScoredForest(Set<PlayerColor> scorers,
                           int points,
                           int mushroomGroupCount,
                           int tileCount) {
  return new StringJoiner(" ")
    .add(scorers.toString())
    .add(String.valueOf(points))
    .add(String.valueOf(mushroomGroupCount))
    .add(String.valueOf(tileCount))
    .toString();
}

(La classe StringJoiner est un bâtisseur de chaînes similaire à StringBuilder, mais qui sépare les différentes chaînes qu'on lui ajoute au moyen du séparateur fourni à son constructeur.)

4. Résumé

Pour cette étape, vous devez :

  • écrire les classes ZonePartitions, ZonePartitions.Builder, MessageBoard et MessageBoard.Message comme décrit plus haut,
  • tester votre code,
  • documenter la totalité des entités publiques que vous avez définies,
  • rendre votre code au plus tard le 15 mars 2024 à 18h00, au moyen du programme Submit.java fourni et des jetons disponibles sur votre page privée.

Ce rendu est un rendu testé, auquel 18 points sont attribués, au prorata des tests unitaires passés avec succès.

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é !