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 :

  1. un programme permettant de jouer à une partie locale, à laquelle des joueurs distants peuvent éventuellement participer,
  2. 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 :

  1. un ou (rarement) plusieurs joueurs humains, dont l'interface graphique s'affiche sur l'ordinateur sur lequel la partie se déroule,
  2. un ou plusieurs joueurs simulés, qui s'exécutent sur l'ordinateur sur lequel la partie se déroule,
  3. 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), ou
  • s pour un joueur simulé (local), ou
  • r 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 :

  1. un joueur simulé nommé Aline, avec 10 000 itérations,
  2. un joueur humain nommé Marie,
  3. un joueur distant nommé Céline dont le serveur s'exécute sur l'ordinateur dont l'adresse IP est 128.178.243.14,
  4. 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 :

  1. la première composante d'une spécification de joueur n'est pas h, s ou r,
  2. la spécification d'un joueur comporte trop de composantes (p.ex. h:Marie:12),
  3. une erreur se produit lors de la connexion au serveur d'un joueur distant,
  4. la graine aléatoire n'est pas un entier long valide,
  5. 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

  1. Arguments du programme

    Pour accéder aux arguments passés au programme depuis la méthode start, il faut utiliser la méthode getParameters() de Application, puis appeler getRaw() sur le résultat afin d'obtenir une liste de chaînes de caractères.

  2. Gestion des erreurs

    Pour transformer une chaîne de caractères en un entier int, vous pouvez utiliser la méthode parseInt de la classe Integer. Notez qu'elle lève l'exception NumberFormatException si la chaîne ne représente pas un entier int 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éthode parseLong de la classe Long.

    Lorsqu'une erreur de connexion au serveur se produit, le constructeur de votre classe RemotePlayerClient devrait lever l'exception IOException (indirectement, car en réalité c'est le constructeur de Socket 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 pas System.out. En effet, le flot System.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 classe System, 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.

  3. 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 de Random 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) :

    1. au constructeur de JassGame,
    2. au constructeur du joueur 1 (PLAYER_1) ssi il s'agit d'un joueur simulé,
    3. au constructeur du joueur 2 (PLAYER_2) ssi il s'agit d'un joueur simulé,
    4. au constructeur du joueur 3 (PLAYER_3) ssi il s'agit d'un joueur simulé,
    5. 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.

  4. 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.

  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 passant true avant de démarrer le fil au moyen de la méthode start. 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 et RemoteMain 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.