Scores
Javass – étape 2

1 Introduction

Le but de cette seconde étape est d'écrire les classes représentant les scores d'une partie de Jass, et d'autres énumérations et classes utilitaires.

Avant de commencer à y travailler, lisez le guide Sauvegarder son travail. Il vous donnera des conseils importants concernant la sauvegarde de votre projet au cours du semestre.

1.1 Scores du Jass

A première vue, représenter les scores d'une partie de Jass peut sembler trivial : une paire de nombres représentant les points obtenus jusqu'à ce point de la partie par chacune des deux équipes devant faire l'affaire.

En réalité, le calcul des scores au Jass est un peu plus complexe que cela, pour deux raisons :

  1. en plus du nombre de points total obtenu par chaque équipe au cours de la partie, il peut être utile d'avoir accès au nombre de points obtenus dans le tour courant, afin de pouvoir les afficher à l'écran,
  2. pour correctement déterminer le score d'une équipe à la fin d'un tour, il faut savoir combien de plis cette équipe a remporté.

La raison pour laquelle le nombre de plis remportés est important est que si une équipe remporte la totalité des 9 plis du tour, alors on dit qu'elle a fait match et elle reçoit 100 points supplémentaires.

Dès lors, les scores d'une partie de Jass sont composés de trois valeurs par équipe — donc six valeurs au total —, qui sont :

  1. le nombre de plis remportés dans le tour courant,
  2. le nombre de points remportés dans le tour courant,
  3. le nombre de points remportés dans la partie courante.

Chacune de ces valeurs est bien entendu positive, et est de plus bornée. La valeur maximale pour chacune d'entre elles est donnée dans la table ci-dessous, accompagnée du nombre de bits nécessaires à la représentation d'une telle valeur :

Valeur Max Bits
Nb de plis 9 4
Points du tour 257 9
Points de la partie 2000 11
Total   24

Le nombre de points maximum qu'il est possible d'obtenir en un tour vaut 257 car une équipe remportant la totalité des plis d'un tour reçoit :

  • les 152 points que valent la totalité des cartes,
  • les 5 points supplémentaires attribués au dernier pli (« 5 de der »),
  • les 100 points supplémentaires du match.

Le nombre de points maximum qu'il est possible d'obtenir en une partie est en réalité inférieur à 2000, mais cette valeur a été retenue pour simplifier les choses.

Sachant qu'un tel triplet de valeurs occupe un total de 24 bits, et que deux triplets de ce type (un par équipe) sont nécessaires à la représentation complète des scores, on conclut que 48 bits suffisent à représenter les scores d'une partie de Jass de manière empaquetée. Une seule valeur de type long (64 bits) suffit donc.

Pour simplifier les choses, on peut attribuer les 32 bits de poids faible au stockage des 24 bits des scores de l'équipe 1, et les 32 bits de poids fort au stockage de ceux de l'équipe 2 :

Bits 63 à 32 31 à 0
Contenu scores de l'équipe 2 scores de l'équipe 1

Chaque paquet de 32 bits est quant à lui découpé en quatre parties, les trois premières contenant les composantes décrites plus haut, la dernière étant vide :

Bits 31 à 24 23 à 13 12 à 4 3 à 0
Contenu 0 points partie points tour nb plis

2 Mise en œuvre Java

2.1 Classe Bits64

La classe Bits64 du paquetage ch.epfl.javass.bits, publique, finale et non instanciable, contient des méthodes statiques permettant de travailler sur des vecteurs de 64 bits stockés dans des valeurs de type long. Elle est très similaire à la classe Bits32 de l'étape précédente, mais n'offre qu'une seule variante de la méthode pack — la seule nécessaire au reste du projet.

Cette classe offre les méthodes publiques (et statiques) suivantes, qui ne sont pas décrites ici car elles sont équivalentes aux méthodes de même nom de la classe Bits32, la seule différence étant qu'elles travaillent sur des entiers de type long :

  • long mask(int start, int size),
  • long extract(long bits, int start, int size),
  • long pack(long v1, int s1, long v2, int s2).

Ces méthodes valident leurs arguments de la même manière que les méthodes équivalentes de la classe Bits32.

2.1.1 Conseils de programmation

Dans la méthode mask, faites bien attention à utiliser la constante 1 de type Long pour construire le masque ! En d'autres termes, écrivez quelque chose comme :

1L << size                      // surtout pas 1 << size !!!

2.2 Type énuméré TeamId

Le type énuméré TeamId du paquetage ch.epfl.javass.jass permet d'identifier chacune des deux équipes. Il ne contient que deux valeurs, qui sont, dans l'ordre :

  • TEAM_1,
  • TEAM_2.

En plus de ces deux valeurs, le type énuméré TeamId possède les attributs statiques et finaux ALL et COUNT, contenant respectivement la liste de toutes les valeurs et leur nombre.

Finalement, TeamId offre la méthode publique suivante :

  • TeamId other(), qui retourne l'autre équipe que celle à laquelle on l'applique (TEAM_2 pour TEAM_1, et inversément).

2.3 Type énuméré PlayerId

Le type énuméré PlayerId du paquetage ch.epfl.javass.jass permet d'identifier chacun des quatre joueurs. Il ne contient que quatre valeurs, qui sont, dans l'ordre :

  • PLAYER_1,
  • PLAYER_2,
  • PLAYER_3,
  • PLAYER_4.

De plus, le type énuméré PlayerId possède les attributs statiques et finaux ALL et COUNT, contenant respectivement la liste de toutes les valeurs et leur nombre.

Finalement, PlayerId offre la méthode publique suivante :

  • TeamId team(), qui retourne (l'identité de) l'équipe à laquelle appartient le joueur auquel on l'applique, à savoir l'équipe 1 pour les joueurs 1 et 3, et l'équipe 2 pour les joueurs 2 et 4.

2.4 Classe PackedScore

La classe PackedScore du paquetage ch.epfl.javass.jass, publique, finale et non instanciable, contient des méthodes statiques permettant de manipuler les scores d'une partie de Jass empaquetés dans un entier de type long selon la technique décrite plus haut.

Elle offre un attribut public, statique et final, utile pour initialiser les scores en début de partie :

  • long INITIAL, qui contient le score initial d'une partie, dont les six composantes valent 0.

En plus de cet attribut, la classe PackedScore offre les méthodes (publiques et statiques) suivantes :

  • boolean isValid(long pkScore), qui retourne vrai ssi la valeur donnée est un score empaqueté valide, c-à-d si les six composantes contiennent des valeurs comprises dans les bornes données plus haut, et les bits inutilisés valent tous 0,
  • long pack(int turnTricks1, int turnPoints1, int gamePoints1, int turnTricks2, int turnPoints2, int gamePoints2), qui empaquète les six composantes des scores dans un entier de type long, turnTricks1 étant le nombre de plis remportés par l'équipe 1 dans le tour courant, turnPoints1 le nombre de points remportés par l'équipe 1 dans le tour courant, et ainsi de suite,
  • int turnTricks(long pkScore, TeamId t), qui retourne le nombre de plis remportés par l'équipe donnée dans le tour courant des scores empaquetés donnés,
  • int turnPoints(long pkScore, TeamId t), qui retourne le nombre de points remportés par l'équipe donnée dans le tour courant des scores empaquetés donnés,
  • int gamePoints(long pkScore, TeamId t), qui retourne le nombre de points reportés par l'équipe donnée dans les tours précédents (sans inclure le tour courant) des scores empaquetés donnés,
  • int totalPoints(long pkScore, TeamId t), qui retourne le nombre total de points remportés par l'équipe donnée dans la partie courante des scores empaquetés donnés, c-à-d la somme des points remportés dans les tours précédents et ceux remportés dans le tour courant,
  • long withAdditionalTrick(long pkScore, TeamId winningTeam, int trickPoints), qui retourne les scores empaquetés donnés mis à jour pour tenir compte du fait que l'équipe winningTeam a remporté un pli valant trickPoints points ; notez que seuls le nombre de plis et le nombre de points du tour courant doivent être mis à jour, et que si cette mise à jour fait que l'équipe gagnante a remporté tous les plis du tour, alors son score doit être augmenté de 100 points additionels étant donné qu'elle a fait match (voir la constante MATCH_ADDITIONAL_POINTS définie dans l'étape précédente) ; par contre, les 5 points attribués au dernier pli ne doivent pas être gérés par cette méthode, ils seront gérés ailleurs,
  • long nextTurn(long pkScore), qui retourne les scores empaquetés donnés mis à jour pour le tour prochain, c-à-d avec les points obtenus par chaque équipe dans le tour courant ajoutés à leur nombre de points remportés lors de la partie, et les deux autres composantes remises à 0,
  • String toString(long pkScore), qui retourne la représentation textuelle des scores, que vous êtes libres de choisir, cette méthode servant uniquement au déboguage ; néanmoins, la méthode toString doit au moins inclure le nombre de points total de chacune des deux équipes, p.ex. séparés par une barre oblique.

2.4.1 Méthodes withAdditionalTrick et nextTurn

Les méthodes withAdditionalTrick et nextTurn ont pour but d'être utilisée pour faire évoluer les scores lors d'une partie. La première est appelée à la fin de chaque pli, tandis que la seconde l'est à la fin de chaque tour.

L'extrait de programme ci-dessous illustre leur fonctionnement en montrant l'évolution des scores lors du premier tour d'une partie fictive dans laquelle les équipes gagnent à tour de rôle, les plis valant tous 18 points sauf le premier qui en vaut 13.

long s = PackedScore.INITIAL;
System.out.println(PackedScore.toString(s));
for (int i = 0; i < Jass.TRICKS_PER_TURN; ++i) {
  int p = (i == 0 ? 13 : 18);
  TeamId w = (i % 2 == 0 ? TEAM_1 : TEAM_2);
  s = PackedScore.withAdditionalTrick(s, w, p);
  System.out.println(PackedScore.toString(s));
}
s = PackedScore.nextTurn(s);
System.out.println(PackedScore.toString(s));

En admettant que la méthode toString affiche les six composantes des scores dans l'ordre, l'extrait de programme ci-dessus imprime ceci :

(0,0,0)/(0,0,0)
(1,13,0)/(0,0,0)
(1,13,0)/(1,18,0)
(2,31,0)/(1,18,0)
(2,31,0)/(2,36,0)
(3,49,0)/(2,36,0)
(3,49,0)/(3,54,0)
(4,67,0)/(3,54,0)
(4,67,0)/(4,72,0)
(5,85,0)/(4,72,0)
(0,0,85)/(0,0,72)

2.4.2 Conseils de programmation

  1. Validation des arguments

    Comme les méthodes de PackedCard, celles de PackedScore n'ont pas l'obligation de valider leurs arguments. Il est toutefois fortement conseillé de le faire au moyen d'assertions.

  2. Méthodes privées

    Pour simplifier et clarifier le code, il est conseillé de définir des méthodes privées permettant de manipuler les groupes de 32 bits contenant les scores d'une équipe, puis de les utiliser pour définir les méthodes publiques décrites plus haut.

2.5 Classe Score

La classe Score du paquetage ch.epfl.javass.jass, publique, finale et immuable, représente les scores d'une partie de Jass.

Tout comme PackedScore, elle offre un attribut public, statique et final représentant le score initial d'une partie :

  • Score INITIAL, qui contient des scores dont les six composantes valent 0.

Tout comme la classe Card de l'étape précédente, la classe Score n'offre pas de constructeur public. Il est néanmoins possible de construire une instance de cette classe au moyen de la méthode publique et statique suivante :

  • Score ofPacked(long packed), qui retourne les scores dont packed est la version empaquetée, ou lève l'exception IllegalArgumentException si cet argument ne représente pas des scores empaquetés valides d'après la méthode isValid de PackedScore.

De plus, la classe Score offre les méthodes publiques suivantes, qui sont pour la plupart équivalentes aux méthodes de même nom de PackedScore. Pour cette raison, leur comportement n'est souvent pas spécifié en détail, mais la manière dont elles valident leurs arguments l'est au besoin :

  • long packed(), qui retourne la version empaquetée des scores,
  • int turnTricks(TeamId t), qui retourne le nombre de plis remportés par l'équipe donnée dans le tour courant du récepteur,
  • int turnPoints(TeamId t), qui retourne le nombre de points remportés par l'équipe donnée dans le tour courant du récepteur,
  • int gamePoints(TeamId t), qui retourne le nombre de points reportés par l'équipe donnée dans les tours précédents (sans inclure le tour courant) du récepteur,
  • int totalPoints(TeamId t), qui retourne le nombre total de points remportés par l'équipe donnée dans la partie courante du récepteur,
  • Score withAdditionalTrick(TeamId winningTeam, int trickPoints), qui se comporte comme la méthode de même nom de PackedScore ou lève l'exception IllegalArgumentException si le nombre de points donné est inférieur à 0,
  • Score nextTurn(), qui se comporte comme la méthode de même nom de PackedScore.

Finalement, la classe Score redéfinit les méthodes equals, hashCode et toString de Object. La méthode hashCode peut simplement retourner la valeur produite par la méthode hashCode de la classe Long, appliquée au score empaqueté.

2.6 Tests

À partir de cette étape, nous ne vous fournissons plus de tests unitaires. Il vous faut donc les écrire vous-même si vous désirez en avoir, ce qui est fortement conseillé.

Si nous ne vous fournissons pas de tests JUnit, nous vous fournissons néanmoins une archive Zip à importer dans votre projet et contenant un fichier nommé SignatureChecks_2.java. La classe qu'il contient fait référence à la totalité des classes, interfaces, types énumérés et méthodes de cette étape, ce qui vous permet de vérifier que leurs noms et types sont corrects. Cela est capital, car la moindre faute à ce niveau empêcherait l'exécution de nos tests unitaires.

Attention : après avoir importé le contenu de cette archive, n'oubliez surtout pas d'ajouter le répertoire sigcheck qu'elle contient au Java Build Path de votre projet, comme expliqué aux points 5 à 7 de notre guide d'importation. Si vous oubliez de faire cette manipulation, Eclipse ne prendra pas en compte le fichier de vérification de signature et ne vous signalera donc pas les erreurs qu'il contient, le rendant totalement inutile.

Nous vous fournirons de tels fichiers pour toutes les étapes jusqu'à la mi-semestre, et il vous faudra penser à vérifier systématiquement qu'Eclipse ne signale aucune erreur à leur sujet. Faute de cela, votre rendu se verra refusé par notre système.

3 Résumé

Pour cette étape, vous devez :

  • écrire les classes et énumérations Bits64, TeamId, PlayerId, PackedScore et Score, selon les indications données ci-dessus,
  • tester votre code,
  • documenter la totalité des entités publiques que vous avez définies,
  • rendre votre code au plus tard le 1er 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é !