Etape 7 – Dessin de l'état
1 Introduction
Le serveur étant désormais capable de faire évoluer l'état d'une partie en fonction des règles et des événements provenant des joueurs, il faut maintenant lui ajouter la capacité de représenter cet état de manière compacte en vue de sa transmissions aux clients. La construction de cette représentation de l'état du jeu est faite en deux phases :
- premièrement, l'état est transformé en une description des images à combiner pour le représenter graphiquement,
- deuxièmement, cette description est compressée et transformée en un tableau d'octets, qui est ensuite envoyé aux clients.
La première de ces phases est le sujet de cette étape, la seconde sera le sujet de la suivante.
1.1 Dessin
Un état quelconque d'une partie de XBlast peut être dessiné à l'écran en combinant un certain nombre d'images existantes représentant les différents éléments du jeu. Par exemple, le plateau peut être dessiné simplement en plaçant côte-à-côte les images des blocs qui le composent, une image par bloc.
Afin de minimiser la quantité de données à envoyer aux clients, il paraît logique de stocker ces images dans les clients et de faire en sorte que le serveur n'envoie qu'une description de la manière dont elles doivent être combinées. Par exemple, à supposer que les images correspondant aux blocs soient numérotées, le serveur peut se contenter d'envoyer au client les 195 numéros des images à utiliser pour le dessin du plateau, plutôt que les images elles-mêmes.
La première phase de la transformation de l'état en vue de son envoi consiste donc justement à déterminer quelles images utiliser pour tous les éléments du jeu : plateau, bombes, explosions et joueurs. Bien que cette phase ne consiste pas réellement en un dessin des images à l'écran (car cela ne sera fait que dans le client), nous la désignerons néanmoins par le terme dessin.
La manière dont les différents éléments du jeu doivent être ainsi « dessinés » est décrite ci-après.
1.2 Dessin du plateau
Le dessin du plateau est relativement simple : à chaque type de bloc correspond exactement une image — à une exception près, comme nous le verrons. Ces images, déjà présentées dans l'énoncé de l'étape 2, le sont à nouveau ci-dessous. Pour mémoire, de gauche à droite, elles représentent : un bloc libre, un mur indestructible, un mur destructible, un mur en train de s'écrouler, un bonus portée, et un bonus bombe.
Figure 1 : Images des blocs (© Oliver Vogel)
En réalité, à un bloc libre correspondent deux images : celle présentée ci-dessus, et une seconde comportant une ombre venant de l'ouest, présentée ci-dessous. Cette version ombrée est utilisée lorsqu'un bloc libre a pour voisin ouest un bloc projetant une ombre. Comme cela a été dit dans l'énoncé de l'étape 2, seuls les murs projettent une ombre.
Figure 2 : Image d'un bloc libre ombré (© Oliver Vogel)
Cet ombrage des blocs donne une impression de profondeur au plateau, subtile mais importante.
1.3 Dessin des bombes
Le dessin des bombes est relativement simple : une bombe est toujours représentée par une image de bombe noire, sauf lorsque la longueur de sa mèche est une puissance de deux, auquel cas une image de bombe blanche est utilisée. Cette technique de dessin provoque un clignotement des bombes, qui s'accélère à l'approche de leur explosion.
Figure 3 : Images des bombes (noire et blanche) (© Oliver Vogel)
1.4 Dessin des explosions
Le dessin des explosions est subtile car la manière dont une particule d'explosion est dessinée dépend de son voisinage. En effet, même si les explosions sont composées de particules discrètes dans le modèle du jeu, elles sont dessinées comme des entités continues.
Etant donné qu'une case possède quatre voisins et que chacun de ces voisins peut être occupé ou non par une particule d'explosion, il y a en tout 24, donc seize, images différentes correspondant à une particule d'explosion. La figure ci-dessous en montre cinq, qui, de gauche à droite, représentent une particule ayant :
- 0 voisins,
- 1 voisin au nord,
- 2 voisins, un au nord, un à l'est,
- 3 voisins, un au nord, un à l'est, un au sud,
- 4 voisins, un au nord, un à l'est, un au sud et un à l'ouest.
Pour faciliter leur visualisation, ces images sont présentées sur un fond gris représentant la case qu'elles occupent, mais elles sont en réalité transparentes.
Figure 4 : Images de particules d'explosion (© Oliver Vogel)
1.5 Dessin des joueurs
Le dessin d'un joueur est relativement complexe car il dépend de :
- son identité (joueur 1 à 4),
- son état (invulnérable, vulnérable, mourant ou mort),
- la direction de son regard,
- sa position.
A chaque joueur correspond donc quatorze images différentes, à savoir :
- trois images par direction de regard, qui ont pour but d'animer le déplacement du joueur, soit un total de douze images,
- deux images pour l'état mourant : une utilisée lorsque le joueur a encore une vie après celle qu'il est en train de perdre, l'autre utilisée lorsqu'il perd sa dernière vie.
De plus, un groupe d'images blanches, correspondant en quelque sorte à un cinquième joueur, est utilisé pour le clignotement. Etant donné que ces images blanches sont identiques pour tous les joueurs, une seule copie est nécessaire.
La figure ci-dessous montre les trois images correspondant au joueur 1 lorsqu'il regarde vers l'est, puis lorsqu'il est mourant — d'abord temporairement, ensuite définitivement. Notez qu'un joueur mourant est toujours dessiné regardant vers le sud, indépendamment de sa direction de regard réelle.
Figure 5 : Quelques images du joueur 1 (© Oliver Vogel)
L'identité du joueur permet de choisir le groupe de quatorze images lui correspondant. Il reste ensuite à choisir une image parmi ces quatorze en fonction de ses paramètres.
S'il est mourant, le choix est simple, puisque il suffit de choisir entre les deux dernières images ci-dessus, en fonction de si le joueur s'apprête à ressusciter ou non.
Si le joueur est invulnérable ou vulnérable, la direction de son regard permet de choisir l'un des quatre sous-groupes de trois images à utiliser, et sa position permet de choisir une de ces trois images. Pour ce faire, la composante de la position correspondant à la direction de regard est utilisée, c-à-d la composante x si le joueur regarde vers l'est ou l'ouest, et la composante y sinon. L'image du sous-groupe de trois à utiliser est donnée par le reste de la division entière de la composante par 4, selon la règle suivante :
- la seconde image est utilisée si ce reste vaut 1,
- la troisième image est utilisée si ce reste vaut 3,
- la première image est utilisée sinon.
Par exemple, admettons que le joueur 1 soit vulnérable, regarde vers l'est, et soit positionné sur la sous-case (27, 24). Etant donné qu'il regarde vers l'est, la coordonnée x de sa position, 27, est utilisée. Le reste de la division de 27 par 4 valant 3, la troisième image du sous-groupe correspondant à la direction est pour le joueur 1 est utilisée. Il s'agit de troisième image depuis la gauche sur la figure 5.
Si le joueur est invulnérable, le choix de l'image se fait comme lorsqu'il est vulnérable, à la différence près que le groupe d'images correspondant au joueur blanc est utilisé si le coup d'horloge est impair, afin de provoquer le clignotement des joueurs dans cet état.
2 Mise en œuvre Java
A partir de cette étape, la première après le rendu intermédiaire, la mise en œuvre Java sera moins guidée que précédemment. En particulier, l'interface publique des classes ne sera plus décrite de manière aussi détaillée qu'avant.
Les noms des classes, interfaces et méthodes donnés ci-dessous ne sont également que des propositions, libre à vous d'en choisir d'autres. Cela dit, afin de ne pas compliquer inutilement le processus de correction, il vous est demandé de ne pas trop vous éloigner de la mise en œuvre que nous vous proposons.
Toutes les classes écrites dans le cadre de cette étape doivent faire partie du paquetage ch.epfl.xblast.server
ou l'un de ses sous-paquetages, car elles ne sont utilisées que par le serveur.
2.1 Images
Pour vous permettre de connaître les numéros correspondant aux différentes images, nous vous fournissons d'ores et déjà une archive Zip contenant les images qui seront utilisées dans le client pour dessiner l'état de la partie. Vous pouvez importer le contenu de cette archive dans votre projet. Une fois cela fait, ajoutez le répertoire images
comme répertoire source, selon les indications données à la fin de notre guide d'importation dans Eclipse.
Les images sont réparties dans différents sous-répertoires du répertoire images
:
block
, qui contient les images des blocs,explosion
, qui contient les images des bombes et des particules d'explosions,player
, qui contient les images des joueurs,score
, qui contient les images du tableau des scores (inutiles dans le cadre de cette étape).
Le nom de chaque image est constitué d'un nombre décimal sur trois chiffres, suivi d'un caractère souligné (_
) puis d'une description textuelle de l'image. Le nombre est celui de l'image, qui doit être produit par les classes décrites ci-dessous.
2.2 Enumération BlockImage
L'énumération BlockImage
énumère les images des blocs, afin qu'il soit possible de les désigner par nom plutôt que par numéro. Bien entendu, pour qu'elle soit utile, il faut que l'ordre de ses éléments soit identique à l'ordre des fichiers du répertoire block
, à savoir : IRON_FLOOR
, IRON_FLOOR_S
, DARK_BLOCK
, EXTRA
, EXTRA_O
, BONUS_BOMB
, BONUS_RANGE
.
La plupart des noms utilisés ci-dessus proviennent de la version originale de XBlast.
2.3 Classe BoardPainter
La classe BoardPainter
, publique, finale et immuable, représente un peintre de plateau. Dans le cadre de ce projet, nous utiliserons le terme de « peintre » pour désigner les objets sachant dessiner les différents éléments du jeu.
Le constructeur de BoardPainter
prend en argument une palette qui décrit quelles images utiliser pour les différents blocs. Cette palette est une table associative de type Map<Block, BlockImage>
associant à chaque bloc l'image à utiliser pour le dessiner. En plus de cette palette, il prend en argument l'image de bloc à utiliser pour les blocs libres ombrés, de type BlockImage
.
Dans le cadre de ce projet, chaque bloc est représenté par une seule image, et la possibilité de paramétrer le peintre de plateau au moyen d'une palette peut donc sembler inutile. Toutefois, la version originale de XBlast utilise différentes images pour les différents niveaux, afin de leur donner une identité propre, et un peintre de plateau configurable permet justement cela.
En plus de son constructeur, la classe BoardPainter
offre une méthode nommée p.ex. byteForCell
qui, étant donné un plateau (de type Board
) et une case (de type Cell
), retourne l'octet (de type byte
) identifiant l'image à utiliser pour la case en question. Attention : n'oubliez pas que les blocs libres sont représentés différemment lorsqu'ils sont à l'ombre d'un mur !
2.4 Classe ExplosionPainter
La classe ExplosionPainter
, publique et finale, représente un peintre de bombes et d'explosions. Contrairement à celui du plateau, ce peintre n'est pas paramétrable. Dès lors, la classe qui le représente n'est pas instanciable (son constructeur par défaut est privé), et offre uniquement des méthodes statiques.
La première de ces méthodes, nommée p.ex. byteForBomb
, retourne l'octet identifiant l'image à utiliser pour dessiner la bombe qu'on lui passe en argument. Comme cela a été expliqué plus haut, il s'agit de l'image de la bombe noire, sauf si la longueur de la mèche est une puissance de deux, auquel cas il s'agit de l'image de la bombe blanche.
Notez qu'il est très facile de déterminer si la longueur de la mèche est une puissance de deux en utilisant la méthode bitCount
de la classe Integer
. A vous de trouver comment faire !
La seconde méthode offerte par ExplosionPainter
, nommée p.ex. byteForBlast
, prend en arguments quatre booléens exprimant la présence ou l'absence d'une particule d'explosion dans chaque case voisine d'une case contenant une particule d'explosion. Elle retourne l'octet correspondant à l'image à utiliser.
Notez que cette seconde méthode peut s'écrire de manière très concise, en tirant parti de la numérotation des images d'explosions, qui a été choisie dans ce but. Observez que si vous faites correspondre à chaque direction (N
, E
, S
et W
) un bit, et que vous combinez ces quatre bits pour obtenir un entier entre 0 et 15, cet entier est le numéro d'image à utiliser. Par exemple, les images de la figure 4 plus haut ont pour index, en binaire et de gauche à droite : 0000, 1000, 1100, 1110 et 1111.
Finalement, la classe ExplosionPainter
offre un champ publique, statique et final nommé p.ex. BYTE_FOR_EMPTY
, qui contient l'octet identifiant l'image à utiliser pour les cases dénuées de particule d'explosion. Aucune image vide n'est fournie, donc nous utilisons simplement par convention un numéro d'image invalide pour les représenter. Dans le cas des explosions, le premier numéro invalide est 16, valeur qui convient très bien ici.
2.5 Classe PlayerPainter
La classe PlayerPainter
, publique et finale, représente un peintre de joueur. Tout comme le peintre d'explosion, celui-ci n'est pas paramétrable, donc représenté par une classe non instanciable dotée de méthodes statiques.
La seule méthode offerte par PlayerPainter
, nommée p.ex. byteForPlayer
, prend en argument un coup d'horloge et un joueur, et retourne l'octet correspondant à l'image à utiliser pour dessiner ce joueur à ce coup d'horloge.
Regardez bien la manière dont les images des joueurs sont numérotées, en examinant le contenu du sous-répertoire player
fourni, avant de vous lancer dans l'écriture de cette méthode. Elle n'est ni très difficile ni très longue à écrire, mais il faut bien penser à prendre tous les paramètres mentionnés plus haut en compte.
Lorsqu'un joueur est mort, utilisez un numéro d'image invalide pour le représenter, par exemple la 15e image de son groupe.
2.6 Tests
A partir de cette étape, aucun fichier de vérification de noms ne vous est fourni, étant donné que les signatures des différentes méthodes n'est plus spécifié en détail.
3 Résumé
Pour cette étape, vous devez :
- écrire les classes des peintres de plateau et de joueur, en fonction des spécifications données plus haut,
- tester votre code,
- documenter la totalité des entités publiques que vous avez définies.
Aucun rendu n'est à faire pour cette étape.