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