TP Noté 2019 : Picross collaboratif

1. Contexte et consignes

1.1 Introduction

Dans ce projet, nous réalisons un serveur et client TCP pour la gestion d'un jeu de « Picross » collaboratif.

Un Picross est un jeu où il faut trouver une image pixel par pixel grâce à des indices fournis sur chaque colonne et chaque ligne de l'image. Il n'est pas utile de comprendre les règles de ce jeu pour réaliser ce TP. Vous devez juste savoir que la fenêtre du jeu se divise en trois zones :

grille de jeu du Pricross
Fig 1 : la grille de jeu et les indices du Picross.

Habituellement, ce jeu se joue en solitaire. L'architecture d'un programme avec interface graphique doit alors contenir trois parties :

Architecture en solitaire
Fig 2 : Architecture du programme pour un jeu en solitaire.

Le but de ce TP est de rendre ce jeu collaboratif en réunissant sur le réseau plusieurs joueurs sur la même grille. L'architecture du nouveau programme ressemble donc à la figure suivante :

Architecture en réseau
Fig 3 : Architecture du programme pour un jeu collaboratif en réseau.

Vous devez écrire les modules client et serveur de ce modèle. Les autres modules sont déjà écrits et fournis.

Tous les échanges réseaux doivent être mis en œuvre avec la bibliothèque JAVA NIO.

La qualité de votre code fait aussi partie de l'évaluation : il doit être lisible, indenté, commenté, et conforme aux exigences SpotBugs (niveau 15) et Checkstyle (fichier fourni pour ce module).

1.2 Consignes

Vous disposer de 3h pour réaliser ce TP. Vous avez le droit aux documents suivants :

Pour installer le sujet :

Le barème est donné à titre d'indication.

En fin de TP :

  1. si ce n'est pas déjà fait, ajouter au fichier readme.txt les informations à destination des correcteurs ;
  2. vérifiez la conformité de vos fichiers aux exigences de SpotBugs ;
  3. vérifiez la conformité de vos fichiers aux exigences de Checkstyle ;
  4. exportez votre projet Eclipse sous la forme d'une archive :
  5. déposez le fichier sur moodle comme devoir ;
  6. vérifiez avec l'enseignant présent dans la salle que le fichier téléchargé est correct.


2. Présentation du sujet

2.1 Introduction

L'objectif est d'écrire les classes qui implémentent le protocole entre le serveur et les clients TCP. Pour cela, vous devez compléter les quatre classes suivantes :

Une dernière question indépendante consiste à modifier l'énumération Protocole.

2.2 Les messages échangés

Tous les messages échangés entre le serveur et les clients sont des trames de taille fixe (MessageProtocole.BUFFERSIZE octets) qui contiennent exactement quatre entiers. Le premier entier correspond au type du message, et les trois autres à des paramètres. Il y a 7 types de messages possibles entre le serveur et les clients. Ils sont listés dans l'énumération Protocole :

  1. TAILLE_GRILLE :
  2. TAILLE_INDICES :
  3. INDICE_COLONNE :
  4. INDICE_LIGNE:
  5. VALEUR_GRILLE_SERVEUR :
  6. GAGNE :
  7. VALEUR_GRILLE_CLIENT :

Les messages du protocole sont construits par la classe MessageProtocole. Elle contient deux constructeurs :

Voici un exemple d'usage pour obtenir un buffer prêt à être envoyé sur le réseau :

Voici un exemple d'usage pour obtenir les informations du message à partir d'un buffer reçu du réseau :

L'action à réaliser à la réception d'un message du protocole est fixée par l'énumération Protocole. Son constructeur associe à chaque énumérateur une instance d'une classe qui implémente l'interface ProtocoleAction. Cette interface demande l'implémentation de la méthode void realiser(PicrossApi picrossApi, int val1, int val2, int val3). Cette méthode doit contenir l'action à faire lors de la réception du message de ce type.

Les classes qui doivent recevoir et traiter les messages ont toutes un attribut (déjà initialisé par le code qui vous est donné) picrossApi contenant la référence à passer en premier paramètre de la méthode realiser(). Le traitement d'un message du protocole se fait donc avec l'appel de la méthode realiser() avec un code qui doit ressembler à ce qui suit :

3. Les 8 questions du sujet

3.1 Introduction

Il y a 8 questions :

Le barème est donné à titre d'indication.

Les 7 premières questions doivent être faites dans l'ordre, mais la dernière question est indépendante et peut être faite à tout moment.

Des tests sont fournis pour les six premières questions. Dans l'archive qui vous est donnée, ils sont désactivés par l'annotation @Ignore. Après l'écriture d'une méthode, pour la tester il faut :

Les tests sont écrits avec deux niveaux de log :

Les logs des tests sont très verbeux, si vous voulez alléger leur sortie, il suffit de régler le niveau du Logger au début de chaque méthode de test.

Les tests ne fonctionnent que si vous faites les questions dans l'ordre.

3.2 Écriture de la classe ConnexionAsynchrone (3pt)

3.2.1 Introduction

La classe ConnexionAsynchrone gère la réception des messages en mode asynchrone. Pour cela, elle associe un buffer (l'attribut buffer de la classe) au canal de la connexion, et fournit des méthodes pour gérer ce buffer.

La lecture d'un message asynchrone peut nécessiter plusieurs appels à la méthode avancerLecture() avant la réception complète du message. Lorsque le message est complètement reçu, la méthode avancerLecture() passe le statut messageComplet à true, et ensuite, la méthode getMessage() peut fournir ce message. La méthode getMessage() doit pouvoir être appelée plusieurs fois pour le même message.

La méthode clear() de la classe doit être appelée avant de commencer la lecture d'un nouveau message.

3.2.2 Création de la connexion asynchrone (1pt)

Écrivez le constructeur de la classe ConnexionAsynchrone. Le paramètre rwChan est le canal déjà connecté. Le constructeur doit rendre la connexion asynchrone et initialiser deux attributs de la classe :

Pour tester votre code, activez le test de la méthode testConnexionAsynchrone() de la classe de test tp4509.tcp.TestConnexionAsynchrone.

3.2.3 Lecture en mode asynchrone (1pt)

Écrivez la méthode avancerLecture() qui doit faire progresser la lecture d'un message du protocole, et ainsi le recevoir complètement appel après appel. Elle signale que le message est complètement reçu en passant le statut de messageComplet à true.

Pour tester votre code, activez le test de la méthode testAvancerLecture() de la classe de test tp4509.tcp.TestConnexionAsynchrone.

3.2.4 Réception du message (1pt)

Écrivez la méthode getMessage() qui fournit un message du protocole s'il est complètement arrivé, et null sinon. La méthode doit pouvoir fournir plusieurs fois le même message.

Pour tester votre code, activez le test de la méthode testGetMessage() de la classe de test tp4509.tcp.TestConnexionAsynchrone.

Attention: le test ci dessus contient une boucle. Si votre méthode est mal écrite cette boucle peut devenir une boucle infinie et le test ne se terminera jamais.

3.2.5 Lecture de la classe

Les autres méthodes de la classe sont déjà écrites ; vous n'avez donc pas à les programmer. Mais, vous devez en utiliser certaines pour la suite du sujet. Donc, prenez le temps de lire et de comprendre les méthodes suivantes :

3.3 Écriture de la classe ThreadClient (7pt)

3.3.1 Introduction

Pour éviter de gérer une connexion asynchrone, l'application cliente (AppliSwing) lance deux threads :

La classe ThreadClient contient la méthode run() de ce thread. Elle possède deux attributs initialisés par le constructeur qui vous est donné :

3.3.2 Écriture de la méthode recevoirMessage() (2pt)

Le rôle principal du thread du client est de recevoir des messages et de réaliser l'action associée. La méthode recevoirMessage() réalise la réception d'un message en mode synchrone.

Écrivez la méthode recevoirMessage() qui retourne le message une fois qu'il est totalement reçu, ou la valeur null si une exception se produit ou si la connexion est fermée.

Pour tester votre code, activez le test de la méthode testRecevoirMessage() de la classe de test tp4509.tcp.TestThreadClient.

3.3.3 Lancement du thread et écriture de la méthode run() (5pt)

Commencez par écrire les instructions pour lancer le thread:

La classe runnable ThreadClient est déjà partiellement écrite, mais les instructions qui lancent le thread avec cette classe restent à écrire. Ajoutez à la fin de la méthode main() de la classe tp4509.graphique.AppliSwing les intructions pour lancer un nouveau thread qui exécute la méthode run() de la classe ThreadClient.

Ensuite écrivez la méthode run():

La signature de la méthode run() nous est imposée par l'interface Runnable et ne permet donc pas la transmission des exceptions. Il faut traiter dans la méthode toutes les exceptions qui peuvent se produire. Le traitement d'une exception se fera par l'affichage du message de l'exception. Donc, pour les exceptions que votre code lève, veuillez fournir un message clair qui explicite la raison de l'exception.

En mode réseau, l'application graphique ne peut pas s'initialiser tant que les informations sur la taille de la grille et des indices ne sont pas reçues. Le thread de l'application graphique est bloqué par un wait() en attente d'une notification signalant la disponibilité de ces informations. Lorsqu'elles sont reçues et traitées, il faut appeler la méthode notifierFinInitialisation() pour redémarrer le thread de l'application graphique.

La méthode run() déroule le protocole d'une partie de Picross. Elle doit donc ressembler à cela :

Si l'ordre des messages du protocole n'est pas cohérent, il faut lever l'exception ProtocoleException avec un message explicite. L'erreur est considérée comme fatale (fin du thread) s'il s'agit d'un deux premiers messages, mais sinon il faut afficher le message, et continuer le programme.

Écrivez la méthode run(). Si la façon de traiter un message du protocole ne vous semble pas claire, relisez la section 2.2 du sujet.

Pour tester votre code pour les situations normales, activez le test de la méthode testRun() de la classe de test tp4509.tcp.TestThreadClient.

Pour tester votre code pour les situations anormales, activez le test de la méthode testRun2() de la classe de test tp4509.tcp.TestThreadClient.

Même si votre code ne passe pas la seconde série de tests, vous pouvez continuer la suite du sujet : votre programme fonctionnera dans les situations normales ; cependant il n'aura pas le comportement attendu en cas d'anomalies dans le protocole.

3.4 Écriture de la classe ServeurPicross (8pt)

3.4.1 Introduction

La classe ServeurPicross contient un serveur TCP multiclient en mode asynchrone. Vous devez écrire deux méthodes :

La méthode envoyerPicross() vous est donnée. Elle contient le code qui envoie toutes les informations utiles à un client qui se connecte.

3.4.2 Écriture de la méthode createServerSocket() (2pt)

Écrivez le code qui crée un serveur TCP asynchrone en attente sur le port passé en paramètre.

3.4.3 Écriture de la méthode serverLoop() (6pt)

Écrivez la boucle d'un serveur asynchrone en utilisant la classe Selector de JAVA NIO.

Chaque client connecté est représenté par une instance de la classe ConnexionAsynchrone écrites à la question 3.2 de ce sujet.

Cette boucle doit gérer trois types d’événements :

  1. la connexion d'un nouveau client, il faut :
  2. la déconnexion d'un client : il faut le retirer de la liste des clients traités ;
  3. la réception du message VALEUR_GRILLE_CLIENT de la part d'un client, il faut :

Cette boucle s'arrête quand la partie est gagnée (picrossApi.estGagne() retourne true). Il faut alors envoyer le message GAGNE à tous les clients.

Écrivez la méthode serverLoop().

Une fois cette méthode écrite, le programme est terminé et il est possible de le lancer. Pour le tester, voici la procédure :

solution du Pricross
Fig 4 : solution de la grille exemple.

Faites la question 3.5 avant de tenter les tests ci dessous.

Pour tester la robustesse de votre application, vérifiez que l'utilisateur ne subit pas une pile d'exception, mais peut soit continuer la partie normalement, soit être averti par un message clair de la raison de la fin du programme pour les cas suivants :

3.5 Classe anonyme et expression lambda pour l'énumération Protocole (2pt)

Dans l'énumération Protocole, l'association entre la méthode à appeler et la valeur de l'énumérateur est faite avec le constructeur qui reçoit en paramètre une instance d'un objet qui implémente l'interface fonctionnelle ProtocoleAction. Dans le code actuel, toutes les constructions se font grâce à des classes d'implémentation définies de façon explicite dans le paquetage tp4509.tcp.protocole_action.

Modifiez la construction de l'énumérateur TAILLE_GRILLE en utilisant une classe anonyme à la place de la classe TailleGrilleAction.

Modifiez la construction de l'énumérateur TAILLE_INDICES en utilisant une expression lambda à la place de la classe TailleIndicesAction.

Supprimez les classes TailleGrilleAction et TailleIndicesAction (et leur ligne d'import) dans le paquetage tp4509.tcp.protocole_action et vérifiez que le programme fonctionne encore. Si vous avez besoin de rétablir ces classes, il y a une version de sauvegarde dans le dossier save du projet.

 

 

 


$Date: 2019-05-20 16:51:01 +0200 (lun. 20 mai 2019) $