Prog re19 eseau en Javapdfauthor - LRDE
Transcription
Prog re19 eseau en Javapdfauthor - LRDE
Programmation réseau avec Java Projet – NetFight Olivier Ricou 23 septembre 2003 Le but du projet est de gagner le combat ! Chaque joueur dispose d’une armée composée d’un serveur mère qui peut créer autant de guerrier qu’il y a de joueurs adverses. Les guerriers vont attaquer les serveurs mère ennemis. Le vainqueur est celui dont le serveur mère reste vivant le dernier. Ce document et les Interfaces sont disponibles sur http://www.lrde.epita.fr/~ricou. Table des matières 1 Les règles 1 1.1 Le serveur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 Triche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.3 Les guerriers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.4 Diplomatie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2 Les Interfaces 4 3 Lancement d’une partie 10 4 Rendu 12 1 1 Les règles 1.1 Le serveur La fiche descriptive d’un serveur est la suivante : – Nombre de points de vie, pdv, en début de partie : 100 – Armure, A, entre 0 et 5 – Force d’attaque, F , entre 5 et 10. Contrainte : F + A ≤ 10 Un serveur perd des point de vie pour chacune de ces actions : – – – – Création d’un guerrier : −10 pdv Destruction d’un guerrier ennemi : −15 pdv Attaque effectuée par l’un de ses guerriers : −2 pdv Attaque reçue d’un guerrier ennemi : −(x − A) pdv avec x la force du guerrier ennemi. Un joueur, donc son serveur, gagne autre joueur. 100 N pdv, avec N le nombre de joueurs, lorsqu’il tue un Création d’un guerrier Le serveur peut créer des guerriers pour attaquer les serveurs ennemis. La force du guerrier est celle du serveur au moment où il le crée. Le serveur créant un guerrier perd immédiatement 10 points de vie. On ne peut envoyer qu’un seul guerrier à la fois attaquer un serveur ennemi. Si son guerrier est tué, le serveur peut en envoyer un autre. Modification des caractéristiques Le serveur peut changer sa force d’attaque et son armure d’un point si la dernière modification date de plus de 30 secondes. Il doit bien sur respecter les contraintes indiquées ci-dessus. Le fait de changer la force du serveur n’influence en rien celle des guerriers déjà créés. Accueil des guerriers ennemis Le serveur doit accueillir tout guerrier qui demande à l’attaquer (et oui, c’est comme ça), avec un maximum d’un guerrier par joueur. public void host(Fighter f) throws RemoteException; Chaque guerrier accueilli doit avoir la main pendant N5 secondes non fractionnables toutes les 10 secondes avec N le nombre de joueurs dans la partie. public void action(int ms); 2 Ainsi dans une partie à 5 joueurs, si un guerrier n’a pas eu la main 9 secondes après son arrivée ou après la dernière fois qu’il a eut la main, il peut déclarer que le serveur qu’il attaque triche. Prise de claque Lorsqu’il donne la main à un guerrier, le serveur a des grandes chances de subir une attaque. public void damage(int d) throws RemoteException; Le serveur doit faire baisser la valeur de ses points de vie d’autant qu’il est indiqué en argument moins son armure, dans un délai de 10 secondes. C’est le seul cas où on peut ne pas appliquer une perte immédiatement. Cela veut dire en particulier que le serveur de l’attaquant perd ses 2 points de vie immédiatement donc avant le serveur victime. Si l’attaquant n’a pas d’allié, alors la valeur donnée doit être équivalente à la force d’attaque du guerrier laquelle était équivalente à celle de son serveur mère lors de sa création. Tuer un nuisible Le serveur peut tuer quand il veut un guerrier ennemi qu’il a accueilli. Bien sûr rien n’interdit au joueur dont le guerrier vient d’être tué, d’en renvoyer un autre. Fin Lorsqu’un serveur meure, i.e. passe en dessous de 0 point de vie, il doit l’annoncer aux guerriers qu’il héberge et leur dire quel est le guerrier qui l’a tué. Le serveur de ce guerrier gagne immédiatement 100 N points de vie (arrondi au nombre supérieur). Tous les guerriers qui attaquaient le serveur tué, meurent eux aussi dans l’explosion du serveur... Tous les guerriers du serveur qui meure, meurent aussi, où qu’ils soient. 1.2 Triche Aucune méthode ne doit accaparer le CPU. Il n’y a pas de raison a priori que la charge du CPU monte trop, si tel était le cas, je rechercherai la raison dans le code. Toute boucle infinie ou autre astuce pour ralentir la machine pendant qu’un autre a officiellement la main, est de la triche. Tout joueur qui note une charge de CPU trop élevée pendant qu’il a officiellement la main, doit déclarer qu’il détecte une triche. Il est du rôle des joueurs de surveiller que les autres joueurs ne trichent pas. Les joueurs peuvent envoyer les informations qu’ils désirent à l’arbitre durant la partie. L’arbitre est un objet de type Ruler récupérable sur le serveur Jini (cf section sur les interfaces). En cas de triche, le joueur devra avertir l’arbitre qui arrêtera la partie et vérifiera, en fonction des données qu’il a reçu, s’il y a eut ou pas triche. 3 Si un joueur annonce une triche qui n’existe pas, il perd la partie. Si un joueur a vraiment triché, il est éliminé de la compétition. Si le joueur accusé semble vraiment avoir triché par incompétence, bug, il ne perd que la partie (mais sa note chutera...). 1.3 Les guerriers Les guerriers sont des programmes hébergés par les serveurs ennemis. Ils ont la main régulièrement et peuvent pendant ce temps faire ce qu’ils veulent sauf des astuces pour accaparer tout le CPU. Les guerriers doivent rendre la main à la fin de leur temps (1 seconde par défaut). Un guerrier peut : – – – – – attaquer le serveur qui l’héberge connaı̂tre les valeurs du serveur qui l’héberge, communiquer avec son serveur mère, ne rien faire :) rentrer à la maison. 1.4 Diplomatie Partie retirée. Donc pas de diplomatie à faire... 2 Les Interfaces Vous devez bien sûr implémenter les 2 premières classes pour être compatible avec les autres joueurs, même si certaines méthodes comme communication peut être vide puisqu’à usage personnel (vous pouvez préférer faire les communications entre vos guerriers et votre home serveur avec des sockets). HomeServer.java 1 2 3 4 5 6 7 8 9 package netfight; import java.io.Serializable; import java.rmi.Remote; import java.rmi.RemoteException; /** Of course, your HomeServer should implements all the Interfaces 4 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 to be concrete. <p/> The Home server should register itself in the available Jini register service (Reggie) using a broadcast to discover it. <p/> The HomeServer should kill all his fighters and exit when interrupted. */ public interface HomeServer extends Remote, Serializable { /** when a HomeServer receives a Fighter, it must : <ul> <li> run its "setAttackedServer" method with "this" as argument,</li> <li> active it regularly with its "action" method .</li> </ul> */ public void host(Fighter f) throws RemoteException; /** the fighter leaves !!! */ public void ciao(Fighter f) throws RemoteException; /** damages d received from a fighter (it is the job of the HomeServer to substract its armor to the damages) */ public void damage(int d, Fighter from) throws RemoteException; /** set the number of players of the game. Should be used only once by StartGame. */ public void setNbPlayers(int n) throws RemoteException; /** set the name of all the players of the current game */ public void setPlayers(String[] players) throws RemoteException; /** tells which one in the array of players I am */ public void setPlayerId(int id) throws RemoteException; /** give the number of life points it still has */ public int getLifePoints() throws RemoteException; /** give the value of its armor */ public int getArmor() throws RemoteException; /** give the value of its strength of attack */ public int getStrength() throws RemoteException; /** give the list of the fighters it hosts. A fighter is known by the name (login) of its owner. */ public Fighter[] getListOfFighters() throws RemoteException; 5 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 /** give the name of the player (owner) of this HomeServer */ public String getPlayerName() throws RemoteException; /** give the id of the player (owner) of this HomeServer */ public int getPlayerId() throws RemoteException; /** method run by one of our figthers to tell us something */ public void communication(String message) throws RemoteException; /** starts the RMI service */ public void startService() throws RemoteException; /** to kill the home server. Shoud be used only by the ruler. The home server should send a info message (not an alert message) to the ruler before it dies. */ public void die() throws RemoteException; } Fighter.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package netfight; import java.rmi.RemoteException; public interface Fighter { /** Name of the owner of this fighter. It must be the login of the player (ricou_o in my case) */ public String getOwnerName(); /** Each player receive un id number for a run (cf StartGame) */ public int getOwnerId(); /** set the HomeServer to which the fighter will make dommages */ public void setAttackedServer(HomeServer victim); /** Activated by the server for a defined number of milisecondes. During it’s actived time, the fighter can <ul> <li> attack the server,</li> <li> ask for information about the server, </li> <li> communicate with its own server,</li> <li> communicate with the other figthers attacking this server,</li> <li> do nothing,</li> <li> sign a treaty with the server,</li> <li> quit to go back home.</li> 6 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 </ul> */ public void action(int ms); /** The server can kill a fighter, it will cost it (cf rules) */ public void die(); /** The server has explosed. It gives the name of its killer. All the fighters hosted die also. */ public void die(Fighter killer); /** method run by our home server to tell us something */ public void communication(String message) throws RemoteException; } L’interface qui suit est celle de l’arbitre que vous devez contacter si vous constatez de la triche. Ruler.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package netfight; import java.io.Serializable; import java.rmi.Remote; import java.rmi.RemoteException; public interface Ruler extends Remote { /** each player can send information to the ruler. The name should be the name (login) of the player. */ public void info(String name, String what) throws RemoteException; /** someone cheat !! Stop everything */ public void alert(String name, String what) throws RemoteException; } Une implémentation possible d’arbitre : RulerServer.java 7 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package netfight; import java.rmi.*; import java.rmi.server.UnicastRemoteObject; import java.rmi.RMISecurityManager; import import import import import import import import net.jini.core.discovery.LookupLocator; net.jini.core.lookup.ServiceRegistrar; net.jini.core.lookup.ServiceRegistration; net.jini.core.lookup.ServiceItem; net.jini.core.lookup.ServiceMatches; net.jini.config.Configuration; net.jini.config.ConfigurationProvider; net.jini.export.Exporter; public class RulerServer extends UnicastRemoteObject implements Ruler { private static final String[] configArgs = new String[] {"jeri.config"}; public RulerServer() throws RemoteException { ServiceMatches matches; Exporter exporter = null; Remote stub = null; ServiceRegistrar registrar = null; ServiceRegistration reg = null; // our service registred // set up the security manager if (System.getSecurityManager() == null) System.setSecurityManager(new SecurityManager()); try { Configuration config = ConfigurationProvider.getInstance(configArgs); exporter = (Exporter) config.getEntry( "NetFight","exporter",Exporter.class); stub = exporter.export(this); registrar = FindRegister.getOneRegistrar(); } catch (Exception e) { System.out.println("Setup exception: " + e.toString()); System.exit(1); } ServiceItem item = new ServiceItem(null, stub, null); try 8 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 { reg = registrar.register(item, 350000L); // 5mn } catch (Exception e) { System.out.println("Register exception: " + e.toString()); System.exit(1); } System.out.println("Registred and ready"); setLog(System.out); } public void info(String name, String what) { System.out.println("INFO : "+name+" : "+what); } public void alert(String name, String what) { System.out.println("ALERT : "+name+" : "+what); System.exit(1); } public static void main(String[] arg) { try { RulerServer ruler = new RulerServer(); Thread.currentThread().sleep(600000); // 10mn } catch (Exception e) {System.out.println("Caramba ! raté : "+e);} } } FindRegister.java 1 2 3 4 5 6 7 8 9 10 11 package netfight; import net.jini.discovery.LookupDiscovery; import net.jini.core.lookup.ServiceRegistrar; public class FindRegister { static public ServiceRegistrar getOneRegistrar() { System.setSecurityManager(new java.rmi.RMISecurityManager()); LookupDiscovery discover = null; 9 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 3 try { discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS); while (discover.getRegistrars().length == 0) Thread.currentThread().sleep(500); } catch(Exception e) { System.err.println(e.toString()); e.printStackTrace(); System.exit(1); } return discover.getRegistrars()[0]; } } Lancement d’une partie Préparation Chaque joueur doit fournir un fichier .jar avec l’ensemble de ses fichiers. Le nom du fichier doit être netfight_<login>.jar. Toutes les classes faisant partie du package netfight. Avant de lancer la moindre partie, j’enregistre dans le serveur Jini le HomeServer de chaque joueur. Pour cela je fais les commandes suivantes avec votre login à la place de ricou_o (à vous de vous débrouiller pour que cela marche) : > jar xvf netfight_ricou_o.jar > cd netfight_ricou_p > make store Votre HomeServeur doit être enregistré avec un attribut (Entry) de type Name contenant votre login et un autre de type Comment avec le mot ”class”. Une partie Lorsque je lance une partie, cf StartGame, je récupère les objets HomeServeurs des joueurs engagés et je les lance localement1 via startService. Cela doit activer leur service RMI afin de pouvoir recevoir des guerrier ennemis. Les souches des services RMI doivent être enregistrées sur le même service Lookup de Jini avec 2 attributs cette fois : – Name("<votre login>") – Comment("<player id>") 1 i.e. dans le thread de StartGame 10 Le second Entry correspond au numéro de joueur que l’on vous donne dans la partie (cela permet d’avoir plusieurs HomeServer du même joueur dans une même partie). Une fois que le joueur a enregistré son service RMI, il doit vérifier que tous les autres joueurs sont enregistrés (leur stub2 , pas leur classe) avant de commencer à jouer/attaquer. En pratique, chaque partie ou match se fait au meilleur des 3 manches. Pour lancer une manche, voici un exemple du type de commande que j’utiliserai (pour le joueur Ricou dans une manche à 3 joueurs ici) : >java -cp netfight_ricou_o.jar -Djava.security.policy=./my.security \ -Djava.rmi.server.codebase=http://127.0.0.1/ \ netfight.StartGame 3 dupont_p durant_h ricou_o 2 avec : StartGame.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 2 package netfight; import import import import import import import java.rmi.RMISecurityManager; net.jini.lookup.entry.Name; net.jini.lookup.entry.Comment; net.jini.core.entry.Entry; net.jini.core.discovery.LookupLocator; net.jini.core.lookup.ServiceRegistrar; net.jini.core.lookup.ServiceTemplate; public class StartGame { public static void main(String[] arg) { int nb_players, player_id; HomeServer player = null; System.setSecurityManager(new RMISecurityManager()); nb_players = (new Float(arg[0])).intValue(); player_id = (new Float(arg[nb_players+1])).intValue(); if (arg.length != nb_players+2) { System.out.println(arg.length); // player id = 0 for the first player System.out.println("arguments : <nb> <nb login joueur> <player id>"); System.exit(1); } RemoteMethodControl.class 11 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 4 try { ServiceRegistrar registrar = FindRegister.getOneRegistrar(); Class[] classes = new Class[] {HomeServer.class}; // récupération du serveur mère du joueur indiqué par l’argument final Entry[] attribute = new Entry[] {new Name(arg[player_id+1]), new Comment("class")}; // TODO pas prendre les objets avec Comment ServiceTemplate template = new ServiceTemplate(null, classes, attribute); player = (HomeServer) registrar.lookup(template); player.setNbPlayers(nb_players); String[] players = new String[nb_players]; for (int k=0;k<nb_players;k++) players[k] = arg[k+1]; player.setPlayers(players); player.setPlayerId(player_id); System.out.println("StartGame - Récupéré "+arg[player_id+1]); // tout va bien, alors on lance le joueur indiqué par l’argument final if (player != null) player.startService(); else System.out.println("Plouf rien n’est parti..."); } catch(Exception e) { System.out.println(" StartGame "+e); System.exit(2); } } } Rendu Le rendu doit être sous la forme d’une tarball bzippée, envoyé avant la fin du mois (30 septembre minuit) en pièce attachée à [email protected]. Les fichiers doivent être dans le répertoire netfight_<login> (donc netfight_ricou_o pour moi). Ce répertoire doit aussi contenir un Makefile qui permet de lancer : – make jar lance la création du fichier netfight_<login>.jar. Cette création du jar doit être suivie de la copie des classes spécifiques à votre guerrier (attention à avoir un nom spécifique/personnel pour vos classes !) dans $WWW_HOME/netfight/ – make store lance l’enregistrement de votre HomeServeur dans Jini. 12