Programmes principaux
Javass – étape 11
1 Introduction
Le but de cette étape est de terminer le projet en écrivant les classes contenant les deux programmes principaux qui permettent de jouer au Jass et qui sont :
- un programme permettant de jouer à une partie locale, à laquelle des joueurs distants peuvent éventuellement participer,
- un programme permettant de participer à une partie distante, qui se déroule sur un autre ordinateur.
1.1 Partie locale
Une partie locale est une partie qui se déroule sur l'ordinateur sur lequel on exécute le programme correspondant et à laquelle participent quatre joueurs, qui peuvent être de trois types différents :
- un ou (rarement) plusieurs joueurs humains, dont l'interface graphique s'affiche sur l'ordinateur sur lequel la partie se déroule,
- un ou plusieurs joueurs simulés, qui s'exécutent sur l'ordinateur sur lequel la partie se déroule,
- un ou plusieurs joueurs distants, qui s'exécutent sur un autre ordinateur, et qui peuvent être d'un type quelconque (mais en général humain).
1.1.1 Configuration
La configuration d'une partie locale se fait en passant des arguments au programme correspondant. Depuis Eclipse, cela peut se faire de la manière décrite dans notre guide sur l'exécution de programmes.
Le programme accepte 4 ou 5 arguments. Les 4 premiers spécifient les joueurs, et le dernier, optionnel, spécifie la graine à utiliser pour générer les graines des différents générateurs aléatoires du programme, selon la technique décrite à la §2.1.
Chaque joueur est spécifié au moyen d'une chaîne de caractères composée de une à trois composantes, séparées les unes des autres par un deux-points (:
). La première composante ne comporte qu'une seule lettre, qui peut être :
h
pour un joueur humain (local), ous
pour un joueur simulé (local), our
pour un joueur distant.
La seconde composante, optionnelle, est le nom du joueur. Par défaut, les noms suivants sont attribués aux joueurs, dans l'ordre : Aline, Bastien, Colette et David.
La troisième composante, elle aussi optionnelle, dépend du type de joueur :
- pour un joueur simulé, elle donne le nombre d'itérations de l'algorithme MCTS (10 000 par défaut),
- pour un joueur distant, elle donne le nom ou l'adresse IP de l'hôte sur lequel le serveur du joueur s'exécute (
localhost
par défaut).
Par exemple, les arguments suivants :
s h:Marie r:Céline:128.178.243.14 s::20000
spécifient une partie à laquelle participent les joueurs suivants :
- un joueur simulé nommé Aline, avec 10 000 itérations,
- un joueur humain nommé Marie,
- un joueur distant nommé Céline dont le serveur s'exécute sur l'ordinateur dont l'adresse IP est 128.178.243.14,
- un joueur simulé nommé David, avec 20 000 itérations.
1.1.2 Paramètres fixes
Pour simplifier les choses, deux aspects d'une partie locale ne sont pas configurables mais fixés ainsi :
- les joueurs simulés ne jouent jamais leur carte en moins de 2 secondes,
- à la fin de chaque pli, une pause de 1 seconde est observée pour permettre aux joueurs de voir la dernière carte posée.
1.1.3 Gestion des erreurs
Lors de l'analyse des arguments passés au programme, ou lors de la connexion aux serveurs des éventuels joueurs distants, il est possible que des erreurs se produisent. Elles doivent être traitées de la manière décrite ci-après.
Une première erreur qui peut se produire est que le nombre d'arguments passés au programme soit invalide, c-à-d différent de 4 ou 5. Dans ce cas, un message d'aide expliquant comment exécuter correctement le programme est affiché sur la console, et le programme se termine. Le message pourrait commencer ainsi :
Utilisation: java ch.epfl.javass.LocalMain <j1>…<j4> [<graine>] où : <jn> spécifie le joueur n, ainsi: h:<nom> un joueur humain nommé <nom> …
Attention : le message affiché en réalité doit être plus long et expliquer la totalité des types de joueurs et des arguments qu'il est possible de spécifier.
Les autres erreurs qui peuvent se produire sont :
- la première composante d'une spécification de joueur n'est pas
h
,s
our
, - la spécification d'un joueur comporte trop de composantes (p.ex.
h:Marie:12
), - une erreur se produit lors de la connexion au serveur d'un joueur distant,
- la graine aléatoire n'est pas un entier
long
valide, - le nombre d'itérations d'un joueur simulé n'est pas un entier
int
valide, ou est inférieur à 10.
Lorsqu'une de ces erreurs se produit, un message d'erreur concis mais clair est affiché sur la console, puis le programme se termine. Par exemple, appelé avec les arguments invalides suivants :
bla bli blo blu
le programme affiche sur la console un message ressemblant à :
Erreur : spécification de joueur invalide : bla
et se termine immédiatement.
Si les arguments comportent plusieurs erreurs, comme dans cet exemple, il suffit que l'une d'entre elles soit affichée.
1.2 Partie distante
Une partie distante est une partie qui se déroule sur un ordinateur autre que celui du joueur. La participation à une telle partie est très simple, car il n'y a aucune configuration à faire : comme nous l'avons vu, celle-ci est faite au niveau de la partie locale.
Même s'il serait possible, et simple, d'offrir la possibilité à des joueurs simulés de jouer à distance, cela ne présente pas de véritable intérêt. Dès lors, nous ne permettrons que à des joueurs humains de jouer à distance.
Le programme de jeu à distance ne prend donc aucun argument, et au démarrage il affiche un message court sur la console pour informer le joueur humain que la partie commencera dès que le client correspondant de la partie distante se sera connecté au serveur. Ce message pourrait ressembler à :
La partie commencera à la connexion du client...
La fenêtre graphique s'ouvre dès que le client s'est connecté au serveur et lui a envoyé le message PLRS
lui communiquant l'identité des joueurs.
2 Mise en œuvre Java
Attention : les classes LocalMain
et RemoteMain
décrites ci-dessous sont les classes principales de votre programme, et il est donc capital qu'elles portent effectivement le nom proposé, et qu'elles se trouvent dans le paquetage ch.epfl.javass
. Notre système de rendu refusera votre projet s'il ne contient pas ces deux classes, si elles sont placées dans un autre paquetage, ou si elles sont nommées différemment.
2.1 Classe LocalMain
La classe LocalMain
du paquetage ch.epfl.javass
contient le programme principal permettant de jouer une partie locale.
Comme ce programme est doté d'une interface graphique JavaFX, la classe LocalMain
hérite de Application
. Comme toujours, sa méthode (statique) main
ne fait rien d'autre qu'appeler la méthode launch
de Application
, et le code démarrant effectivement l'application se trouve dans la méthode start
.
La méthode start
analyse les arguments et crée les 4 joueurs en fonction, en signalant bien entendu les éventuelles erreurs rencontrées. Si les 4 joueurs peuvent être créés sans problème, un fil d'exécution séparé est démarré, dans lequel la partie se déroule.
2.1.1 Conseils de programmation
- Arguments du programme
Pour accéder aux arguments passés au programme depuis la méthode
start
, il faut utiliser la méthodegetParameters()
deApplication
, puis appelergetRaw()
sur le résultat afin d'obtenir une liste de chaînes de caractères. - Gestion des erreurs
Pour transformer une chaîne de caractères en un entier
int
, vous pouvez utiliser la méthodeparseInt
de la classeInteger
. Notez qu'elle lève l'exceptionNumberFormatException
si la chaîne ne représente pas un entierint
valide, exception qu'il vous faut donc rattraper afin d'afficher un message d'erreur.Pour l'entier
long
représentant la graine, vous pouvez utiliser de la même manière la méthodeparseLong
de la classeLong
.Lorsqu'une erreur de connexion au serveur se produit, le constructeur de votre classe
RemotePlayerClient
devrait lever l'exceptionIOException
(indirectement, car en réalité c'est le constructeur deSocket
qui la lève), qu'il vous faut également rattraper pour afficher un message d'erreur.Les autres erreurs ne sont pas signalées au moyen d'exceptions, il vous faut donc les détecter vous-même, et afficher un message d'erreur au besoin.
Dans tous les cas, les messages d'erreurs doivent être affichés sur le flot
System.err
et pasSystem.out
. En effet, le flotSystem.err
est dédié aux messages d'erreur.De plus, lorsqu'une erreur est détectée, le programme doit se terminer immédiatement, ce qui peut se faire en appelant la méthode
exit
de la classeSystem
, en lui passant 1 en argument. Cet argument est le code de retour du programme, et toute valeur différente de 0 indique par convention que le programme s'est terminé anormalement. - Graines de générateurs aléatoires
Pour faciliter les tests, le programme principal permettant de jouer une partie locale prend un argument optionnel, qui représente une graine de générateur aléatoire.
Le but de cette graine est d'être utilisée pour initialiser un générateur aléatoire principal, utilisé ensuite pour obtenir les graines des autres générateurs aléatoires du projet.
Si l'argument optionnel représentant la graine est fourni au programme principal, alors le générateur aléatoire principal doit être obtenu en passant cette graine au constructeur de
Random
prenant une graine en argument. Par contre, si cet argument n'est pas fourni, alors le générateur aléatoire principal doit être obtenu au moyen du constructeur deRandom
ne prenant aucun argument, ce qui implique que sa graine sera aléatoire.Une fois construit, le générateur aléatoire principal doit être utilisé pour générer exactement 5 valeurs de type
long
, qui doivent être passées (dans l'ordre) :- au constructeur de
JassGame
, - au constructeur du joueur 1 (
PLAYER_1
) ssi il s'agit d'un joueur simulé, - au constructeur du joueur 2 (
PLAYER_2
) ssi il s'agit d'un joueur simulé, - au constructeur du joueur 3 (
PLAYER_3
) ssi il s'agit d'un joueur simulé, - au constructeur du joueur 4 (
PLAYER_4
) ssi il s'agit d'un joueur simulé.
On garantit ainsi qu'en passant la même graine au programme, on obtienne toujours la même partie. D'autre part, étant donné que la manière dont
JassGame
utilise la graine qu'on lui passe pour choisir l'atout et mélanger les cartes a été clairement spécifiée à l'étape 5, une graine donnée devrait produire une même distribution des cartes, et un même choix de l'atout pour tous les projets. - au constructeur de
- Ralentissement des joueurs simulés
Pour garantir que les joueurs simulés ne jouent jamais une carte en moins de 2 secondes, il faut bien entendu utiliser la classe
PacedPlayer
écrite lors de l'étape 5. - Fil d'exécution
Comme illustré dans le programme d'exemple donné à la fin de l'étape 10, la partie doit se dérouler dans un fil d'exécution séparé. Afin que l'application se termine correctement, il est nécessaire d'appeler la méthode
setDaemon
en lui passanttrue
avant de démarrer le fil au moyen de la méthodestart
. En d'autres termes, le fil doit être créé et démarré ainsi :Thread gameThread = new Thread(/* … */); gameThread.setDaemon(true); gameThread.start();
2.2 Classe RemoteMain
La classe RemoteMain
du paquetage ch.epfl.javass
contient le programme principal permettant de jouer à une partie distante.
Comme ce programme est doté d'une interface graphique JavaFX, la classe RemoteMain
hérite également de Application
.
Sa méthode start
est très simple et ne fait rien d'autre que créer un fil d'exécution séparé exécutant un serveur, puis afficher le message mentionné plus haut (« La partie commencera à la connexion du client… ») sur la console.
Attention, le fil exécutant le serveur doit être créé de la même manière que celui dans laquelle se déroule la partie, c-à-d avec un appel à setDaemon
avant l'appel à start
.
2.3 Tests
Etant donné que les classes contenant vos programmes principaux doivent avoir un nom précis, nous vous fournissons à nouveau un fichier de vérification de signatures pour cette étape, contenu comme d'habitude dans une archive Zip à importer dans votre projet.
Pour tester votre projet, vous pouvez bien entendu jouer au Jass contre des joueurs simulés ou d'autres joueurs humains distants. Il est de plus fortement conseillé d'effectuer des tests combinant des clients et serveurs de différents projets, car cela vous permet de vérifier que vous avez bien mis en œuvre le protocole de communication spécifié à l'étape 7. Cela est capital car un des aspect de vos projets que nous testerons est justement qu'ils mettent correctement en œuvre le protocole de communication.
3 Résumé
Pour cette étape, vous devez :
- écrire les classes
LocalMain
etRemoteMain
en fonction des indications données plus haut, - tester votre code,
- documenter la totalité des entités publiques que vous avez définies,
- rendre votre projet dans le cadre du rendu final anticipé, si vous désirez tenter d'obtenir un bonus, ou dans le cadre du rendu final normal sinon ; les instructions concernant ces rendus seront publiées ultérieurement.