Page suivante Page précédente Table des matières

5. Le Client

5.1 Analyse

Les différentes phases du jeu.

Dans le jeu du morpion, les différentes phases du jeu sont :

  1. On choisit un symbole afin de s'identifier sur la grille de jeu.
  2. On attend que ce soit à nous de jouer.
  3. On joue un coup valide, c'est à dire que l'on met notre symbole dans une case libre de la grille.
  4. On continue en reprenant à la phase 3 si la partie continue ou sinon on reprend à la phase 1.

Le client doit donc être en mesure de proposer aux joueurs d'évoluer dans ces différentes phases de jeu, selon l'ordre établi ci-dessus.

Échanges à effectuer avec les autres joueurs.

Le but du projet est de développer une application de jeu du morpion entre utilisateurs de systèmes Unix en réseau. Nous devons donc effectuer différents échanges entre les différents joueurs, afin que les grilles de jeu de chacun des joueurs soient tenues en permanence à jour.

Par ailleurs, l'application doit permettre d'obtenir la liste des parties déjà en cours (ce qui sous-entend qu'il peut exister plusieurs parties), de créer une nouvelle partie (et donc la rendre accessible aux autres joueurs du réseau), de joindre une des parties existantes selon l'accord des autres joueurs (il va donc falloir créer un système de "vote" à travers le réseau).

De plus, l'application doit permettre de signifier aux joueurs d'une même partie s'il y a match nul (la grille est pleine et personne ne gagne) ou si l'un des joueurs a gagné (en alignant 5 signes horizontalement, verticalement ou en diagonale).

Partant de cette étude nous avons donc pu établir le protocole décrit précédemment.

5.2 Implémentation

Dans notre implémentation du jeu, nous avons décidé de confier "l'intelligence" du jeu à un serveur gérant l'ensemble des joueurs, des parties et des coups joués dans ces parties. Ceci nous permet donc d'avoir des clients simples, ne gérant que :

Le client est construit en différents modules articules selon les différentes phases de jeu décrites ci-dessus. On peut donc représenter le fonctionnement du client par le schema suivant :

\begin{figure}[!hbtp] \begin{center} \epsffile{figures/phases_client.eps} \caption{\it Differentes phases de fonctionnement du client} \end{center} \end{figure}

Description des différentes phases

Nick

est la phase durant laquelle le joueur doit saisir son surnom afin de s'identifier auprès du serveur.

ListeParties

est la phase durant laquelle le joueur peut visualiser la liste des parties existantes, et choisir de créer une nouvelle partie ou joindre une partie existante.

NewPartie

durant cette phase le joueur saisi les paramètres de creation de la nouvelle partie, les envoie au serveur et attend l'accord ou le refus de cette creation par le serveur.

JoinPartie

est la phase surant laquelle le joueur demande à joindre une partie existante et attend confirmation ou refus de son intégration par le serveur.

JouePartie

durant cette phase le joueur visualise la grille de jeu et les informations sur les joueurs présents dans la partie. Il est en attente de son tour de jouer ou d'une modification de la grille.

Play

durant cette phase le joueur peut jouer un coup et attend que ce coup soit accepté ou refusé par le serveur.

Grid

est la phase durant laquelle une mise à jour complète de la grille est effectuée.

Win

est la phase indiquant au joueur que la partie a été gagnée.

End

est la phase indiquant au joueur qu'il y a eu match nul.

5.3 Ajout à Tcl de commandes permettant les échanges de messages avec le serveur

Les échanges entre les clients et le serveur de messages conformes au protocole doit se faire à l'aide des sockets. Le serveur :

De son coté le client doit :

Afin d'être le plus indépendant possible de l'architecture, le client est codé en majeure partie en langage script Tcl/Tk. Afin de permettre ces échanges d'informations avec le serveur, nous avons dû ajouter de nouvelles commandes à ce langage script, effectuant les opérations décrites ci-dessus. Ces nouvelles commandes sont :

Les 3 premières de ses fonctions ne présentent aucun problème à l'implémentation car l'instant auquel elles sont utilisées est parfaitement déterminé dans le code du client. Par contre, il n'en est pas de même avec la fonction de lecture des messages en provenance du serveur.

En effet les messages envoyés par le serveur peuvent arriver à tout moment sur la connexion du coté client. De plus, comme nous l'avons dit plus haut, ces messages doivent être traités le plus tôt possible de leur arrivée.

Il a donc fallu trouver un moyen permettant à l'interprérteur Tcl d'aller chercher les messages sur la connexion dès que ceux-ci sont disponibles. Pour ce faire nous effectuons l'appel de l'instruction à chaque fois que l'interpréteur Tcl ne fait plus rien (ce qui est quand même très souvent puisque le joueur attend la majeur partie du temps son tour de jouer), grâce à l'instruction :

               after idle { TraiteProtocole [lit_socket] }
qui signifie "quand l'interpréteur ne fait plus rien, alors il va lire un message sur la connexion, puis traite ce message grâce à la procédure TraiteProtocole (écrite en Tcl)".

Cette "banale" phrase fait apparaître un autre problème : la procédure TraiteProtocole ne gère qu'un seul message à la fois. Or en étudiant rapidement le protocole défini, nous nous appercevons que plusieurs messages peuvent arriver à la suite. De plus, les sockets TCP ne garantissent pas du tout que lors d'une lecture, nous obtiendrons l'intégralité d'un message.

Ce problème a été résolu de la manière suivante: nous avons défini un message comme étant une chaîne de caractères terminée par un retour chariot. Il suffit donc de mettre dans un tampon les données lues sur la socket à la suite des données déjà présentes dans ce tampon (qui sont normalement le début du message que nous sommes en train de finir de lire), puis de rechercher le premier retour chariot. Nous connaissons alors le premier message reçu, et en intégralité il ne reste plus qu'à le retourner afin qu'il soit traité par TraiteProtocole. Étant donné que nous devions utiliser la librarie Tcl pour l'ajout de ces nouvelles commandes, nous avons profité des fonctions de gestion d'une chaîne dynamique offertes par la librairie Tcl (fonctions Tcl_DString*), ce qui nous a évité d'avoir à écrire du code, de ce fait, inutile et peu intéressant.

Évidemment, si l'interpréteur va chercher à lire des données à chaque fois qu'il ne fait plus rien (c'est à dire quand il n'y a plus de commande Tcl à interpréter, plus d'évènement X à gérer, etc.), il va donc appeler très souvent la commande lit_socket et donc se bloquer en attente de l'arrivée de données sur la socket. Pour remédier à ce problème, nous avons donc introduit l'appel à une instruction nous indiquant si des données sont disponibles sur la socket (à l'aide d'un timeout sur un select). Ainsi si aucune donnée n'est disponible à la lecture sur la socket et que le tampon des messages ne contient pas de message complet, alors nous redonnons immédiatement la main à l'interpréteur Tcl qui pourra alors se consacrer à gérer les évènements X ou tout autre chose.

L'avantage de cette implémentation est de garder l'indépendance à l'architecture, de laisser le shell maître de ses opérations, et surtout d'éviter d'avoir un processus client passant l'intégrale de son temps à lire sur la socket, au détriment de la gestion des évènements X sur l'interface homme/machine, et consommant une grande partie des ressources de la machine à ne rien faire.


Page suivante Page précédente Table des matières