Les IA:

    Les 'IA' sont l'intelligence des bestioles; ce sont elles qui, dans un monde donné, vont décider des actions à effectuer pour survivre. Il est possible de les programmer, ou de configurer une IA déja existante (si celle ci peut être configurée...).

    Les fichiers contenant ces IA sont des .class java (des programmes java compilés), et se trouvent dans le repertoire 'iaengine/user'. Celles qui peuvent être configurées à travers une interface possèdent en plus un fichier pour l'IHM, qui se trouve dans 'iaengine/user/configuig'.

    Une IA ne fait que calculer une proposition d'action pour une bestiole. Elle ne modifie JAMAIS la bestiole elle-même (ex : sa position pour une action de déplacement). En fait, toute action calculée est d'abord envoyée au monde pour approbation, et c'est le manager, lorsqu'il reçoit l'accord du monde, qui execute l'action pour la bestiole concernée.

    Les bugs dans cette classe peuvent coûter très cher à la bestiole. Si par exemple, l'action calculée par l'IA est de manger une nourriture inexistante, l'action sera refusée. La bestiole peut donc rester immobile, ce qui en fera une proie facile pour les autres. Les mondes envoient des messages de refus d'actions précisant en quoi la décision de l'IA était erronée; ce qui peut être utile pour le débugage.

    Deux astuces peuvent être essayées dans les IA (astuces que je n'ai pas codé dans les IA par défaut) :
    - il est possible de créer une "mémoire" de la bestiole en conservant les parties du monde vues à chaque action, et donc d'améliorer l'IA.
    - il doit aussi etre possible, en mettant des données en 'static' (Cf. vos cours de Java), de mettre en commun des données de chaque IA, pour en créer une globale qui régit toutes les bestioles en tant que groupe. A essayer, donc...

    Les cadres qui suivent donnent des informations techniques et des obligations à suivre pour programmer sa propre IA.

 

Le codage d'une IA:

    Une IA n'a qu'un accès limité à l'instance de la bestiole qu'il contrôle, par l'intermédiare de la classe 'bestioleFacade'. Une IA est sensée pouvoir être utilisée pour chacune des trois espèces de NeoNoe (celles ci n'ont pas les mêmes IAs (pour ce qui est des combats et de l'alimentation)), et pour les mondes ouverts ou les arènes (les sauts entre deux domaines, en particulier, sont impossible dans les arènes). Pour des raisons de sécurité, les droits des IA sont très limités: elles ne peuvent accèder au disque, au réseau, etc.

    Si vous utilisez des sous-classes pour votre IA: soit elles sont placées dans un repertoire, lui même placé dans le repertoire des IAs, (ex: "iaengine/user/mon_ia_classes/mon_ia_fichier1.class"), soit vous les codez en interne à la classe principale (et les .class générés auront le nom "classePrincipale$classeInterne.class" et seront automatiquement reconnues comme classes internes par NeoNoe).

    Vous pouvez programmer votre IA comme vous le voulez, la seule condition est que le moteur de NeoNoe puisse appeler certaines fonctions bien précises. La classe java doit donc hériter de la classe 'iaengine.skeleton.BestioleIA'. Voici ce que doivent faire les fonctions:

public void initIA(BestioleFacade b);
                    Crée les caracteristiques par defaut de l'IA pour la bestiole b. Il faut appeler super.initIA(b) au début de la focntion.

public void initIA(BestioleFacade b, Object[] config);
                    Configure l'IA par rapport aux valeurs 'config'. Ces valeurs sont celles retournées par l'IHM de configuration de l'IA (voir l'IHM de configuration), si elle existe (sinon la fonction ne sera jamais appelée).

public BestioleActionNotification computeAction(World world);
                    Fonction appelée pour calculer l'IA de la bestiole.
                    Le monde passé en paramètre est la portion du monde vue par la bestiole, donc un carré dont la taille du coté est égal à 2 fois la capacité de vision, plus la case de la bestiole. La case centrale de cette portion de monde est celle sur laquelle se trouve la bestiole. (la classe Monde)
                    L'objet retourné est une notification d'action qui sera envoyée au monde. Pas la peine de retourner des actions bidons (du genre avancer de 6000 cases), au mieux les actions de ce genre seront refusées. La liste des action possibles se trouve plus bas.

public String toString();
                    Retourne une description de l'état de l'IA : par exemple, 'en fuite', 'attaque xx', 'cherche a manger'... Ce texte sera visible dans l'écran des informations de la bestiole, dans l'onglet 'IA'.

public void copy(BestioleIA bestioleIA);
                    Initialise l'IA en copiant les réglages de 'bestioleIA'. Utilisé lors du chargement d'un fichier de sauvegarde, ou pour la naissance de bestioles héritant du IA d'un des parents. 'bestioleIA' est TOUJOURS de la même classe que l'instance sur laquelle est appelée cette fonction.

public void doIACross(BestioleIA cb1, BestioleIA cb2);
                    Copie une partie des deux IA cb1 et cb2. Utilisé pour la naissance de bestioles héritant de l'IA commune de ses parents. 'cb1' et 'cb2' sont TOUJOURS de la même classe que l'instance sur laquelle est appelée cette fonction.

public void addToMutationSystem(MutationSystem mutSys);
                    Fonction utilisée pour spécifier les variables de la classe qui seront soumises aux mutations (voir L'IA et l'évolution artificielle).

          L'IHM de configuration de l'IA n'est pas stockée dans la classe elle même, mais dans une classe à part. Il faut indiquer le nom de cette classe dans le champ statique 'iAConfigWindow' de la classe de l'IA.
public static String iAConfigWindow = "BasicIAConfig";

          Les textes apparaissant dans NeoNoe concernant une IA, par exemple la description de la classe (visible dans les fenêtres de configuration des managers et des bestioles pour la selection de l'IA), ou les libellés pour la fenêtre de configuration de l'IA, sont enregistrés dans un fichier texte à part, dans le répertoire 'messages'.
Ce fichier doit avoir pour nom nomIA_langue.properties, avec 'nomIA' le nom de la classe java de l'IA, et 'langue' la langue des textes (fr, en, de...). Le fichier doit contenir au moins la clé DESCRIPTION=... qui sera la description de l'IA visible dans l'interface de NeoNoe. Dans le code Java, ces textes peuvent être récupérés à l'aide du code getMessage("NOM_CLE").

      Par exemple, pour une IA enregistrée dans le fichier monIA.java :
1. Le fichier contenant les textes sera 'messages/monIA_fr.properties' pour les textes en français, 'messages/monIA_en.properties' pour les textes anglais...

2. Il contiendra, par exemple :
#Exemple de fichier contenant les textes nécessaires à une IA
DESCRIPTION=Ceci est la description de mon IA. Elle doit expliquer son fonctionnement pour d'éventuels autres utilisateurs.

#Textes de l'interface
Configuration.TITRE=Le titre de ma fenêtre
Configuration.LibelléChamp1=Entrez la valeur pour xxxx.

[...]

3. Ces textes seront utilisés dans le code Java de la façon suivante (ici, dans le code de l'interface de configuration):
[...]
public BasicIAConfig(String iAClassName, PropertyResourceBundle resourceBundle){
     super( iAClassName, resourceBundle );
     setLayout (new GridLayout(3,0));


     JLabel titre = new JLabel(getMessage("Configuration.TITRE"));
     titre.setFont(NeoNoe.DEFAULT_FONT);
     add(titre);

[...]

 

Les actions possibles:

     Voici la liste des actions qu'il est possible d'envoyer au monde pour que sa bestiole les execute, et leur utilisation (ce n'est pas bien compliqué !):

public BestioleActionNotificationMove(int bestioleID, int dx, int dy);
     Indique un déplacement de la bestiole, de vecteur (dx,dy) cases. L'action est acceptée si :
           1. -capacité_de_marche <= dx <= -capacité_de_marche,
           2. -capacité_de_marche <= dy <= -capacité_de_marche,
           3. 0<= dx + position x < taille x du monde,
           4. 0<= dy + position y < taille y du monde.

      Notes : 1. le coùt en force d'un déplacement est égal à dx+dy.
                   2. dx = 1 représente un déplacement d'une case à droite, dy = 1 d'une case en bas.

public BestioleActionNotificationEat(int idBestiole);
     Indique que la bestiole va consommer la nourriture placée sur sa case. L'action est acceptée si de la nourriture se trouve bien sur la case, et si elle à le droit de la manger : une herbivore ne peut manger de cadavres, une carnivore ne peut manger de nourriture naturelle. Il faut généralement plusieurs tours pour consommer totalement la ressource.

     Note: Une omnivore qui envoie cette notification pour une case qui contient un cadavre et de la nourriture naturelle commence par manger le cadavre. Elle a de plus des malus : elle assimile moins la nourriture par action (pour compenser son aptitude à utiliser toutes les ressources de la carte).

public BestioleActionNotificationAttaq(int idBestiole, int iDCible);
     Indique que la bestiole idBestiole veut attaquer une autre bestiole iDCible sur sa case. L'action est acceptée si une bestiole dont l'identifiant est iDCible se trouve bien sur la case. Détails qui peuvent servir :
           - si idBestiole=iDCible, la bestiole s'attaque elle même... ;-P
           - la bestiole peut perdre son combat et se faire tuer
           - le combat peut être nul, même si les forces ne sont pas exactement égales, à cause du 'flou de combat' du monde. Par exemple, un flou de combat de 10% provoquera des matchs nuls si la bestiole attaquante n'a pas 10% de force de plus que sa cible.
           - les forces des bestioles attaquées sont influencées par le terrain. Par exemple, en montagne, une bestiole à 100% de force en plus en défense. (Ce bonus ne s'applique pas aux attaques.)
           - les forces des bestioles sont influencées par les malus inhérents à leur espèce. Par exemple, une herbivore à un malus de 80% (par défaut, mais cela peut changer suivant les mondes) de sa force si elle se fait attaquer. La meilleure solution pour elle est donc de courir vite!

public BestioleActionNotificationJump(int idBestiole, int dx, int dy, int dz);
     Indique un saut d'un domaine à un autre de la bestiole idBestiole. Les trois valeurs permettent de calculer la 'dimension' du saut (Cf les sauts).
L'action est acceptée si :
           1. dx<=capacité_de_marche et dy<=capacité_de_marche et dz<=capacité_de_marche ,
           2. Le mouvement (dx, dy) fait sortir la bestiole des limites du domaine dans lequel est évolue.
           3. La bestiole n'est pas dans une arène.

Note : le coùt en force d'un saut est égal à dz si le saut se fait sur Z, dx s'il se fait sur X, et dy s'il se fait sur Y.

 

Les sauts entre domaines:

     Les domaines sont disposés sur trois dimensions : les deux premières correspondent aux dimensions des mondes (X = largeur et Y = hauteur) , la troisième à une sorte "épaisseur", comme si les domaines étaient empilés les uns sur les autres (Z). Un saut peut se faire dans deux sens, selon si dx (ou dy ou dz) est supérieur à zéro ou non.
     Un saut sur une dimension, dans un sens, suivit d'un saut sur la même dimension, dans l'autre sens doit ramener la bestiole dans son domaine d'origine (à moins qu'un domaine intermédiare ne se soit créé entre les deux sauts!) . Dans certains cas, un saut peut ramener sur le même domaine (surtout les sauts sur Z).

     Un saut ne peut s'effectuer que sur une seule dimension à la fois : si une bestiole est dans le coin bas-droit de la carte et que son saut est de dx=1 et dy=1, elle sautera dans le domaine "de droite", et non pas dans le domaine d'"en bas à droite".

Le calcul de la dimension se fait dans l'ordre :
     1. si dz != 0 : saut sur la dimension Z.
     2. si dx != 0 : saut sur la dimension X.
     3. si dy != 0 : saut sur la dimension Y.

     La position d'arrivée de la bestiole après un saut dépend de la dimension du saut. Dans le cas d'un saut sur la dimension X, la bestiole arrivera sur le bord gauche (ou droit, selon le sens) du domaine voisin.Sa position en hauteur sera la même position qu'au départ, relativement à la taille du domaine. Un saut sur Z fera atterrir la bestiole sur la même position qu'au départ, relativement à la taille du domaine.
Deux exemples plus parlant, de sauts d'un domaine de taille (10, 10) à un domaine de taille(20,20):
     1. position de départ = (5,9) ==> un saut sur Y amène en position (10,0).
    2. position de départ = (2,5) ==> un saut sur Z amène en position (2x2,2x5) = (4,10).

      Pour info : l'ordre des domaines est décidé à partir des IP et ports des mondes. La dimension Z correspond au port, la dimension X aux deux premiers octets de l'IP, la dimension Y aux deux derniers octets.

 
 

L'API pour les IA

   Aucune objet (monde, bestiole) n'est lié directement à une IA, pour éviter qu'elle ait accès à des fonctions dont elle ne doit pas avoir l'usage. Des classes intermédaire , 'xxxFacade', sont programmées pour limiter ces accès.
 

Les fonctions utiles de la classe World:

     Si vous avez bien suivi, le monde est composé de cases de différents terrains. Ces terrains apportent (ou pas) un bonus de défense, et (ou pas) de la nourriture. Le monde contient aussi, pour chaque case, le liste des bestioles qui y sont placées.

public int getWidth();
     Retourne la largeur du monde.

public int getHeight();
     Retourne la hauteur du monde.

public Ground getGroundAt(int x, int y);
     Retourne l'instance du terrain de la case (x,y).

public int getVegetableFoodSizeAt(int x, int y);
     Retourne la taille de la nourriture naturelle de la case (x,y) (0 si pas de nourriture).

public String getVegetableFoodNameAt(int x, int y);
     Retourne le nom de la nourriture naturelle de la case (x,y) (null si pas de nourriture).

public int getNonNaturalFoodSizeAt(int x, int y);
     Retourne la taille des cadavres de la case (x,y) (0 si pas de nourriture).

public String getNonNaturalFoodNameAt(int x, int y);
     Retourne le nom de la nourritre de la case (x,y) (null si pas de nourriture).

public BasicBestioleList getBestioleListAt(int x, int y);
     Retourne la liste des bestioles présentes sur la case (x,y).

public boolean cellCoordsOK(int x, int y);
     Retourne true si la case (x,y) existe sur le monde.

 

Les fonctions utiles de la classe Ground:

Chaque case de terrain peut contenir de la nourriture.

public int getDefenceBonus();
     Retourne le bonnus de défense (50 => +50%).

public int getGroundName();
     Retourne le nom du terrain.

public int getVegetationFoodSize();
     Retourne la taille de la nourriture naturelle (0 si pas de nourriture).

public String getVegetationFoodName();
     Retourne l'instance de la nourriture naturelle (null si pas de nourriture).

public boolean isVegetationFoodPossible();
     Retourne true si la nourriture naturelle peut pousser sur ce type de terrain.

public boolean isVegetationFood();
     Retourne true s'il y a de la nourriture naturelle sur le terrain.

public int getNonNaturalFoodSize();
     Retourne la taille des cadavres de la case (0 si pas de nourriture).

public NonNaturalFood getNonNaturalFood();
     Retourne l'instance des cadavres de la case (null si pas de nourriture).

 

Les fonctions utiles de la classe Bestiole:

     Les fonctions pour une bestiole sont :

public int getBestioleID();
     Retourne l'identifiant de la bestiole.

public int getIDClient();
     Retourne l'identifiant du client propriétaire de la bestiole.

public int getPower();
     Retourne la force brute de la bestiole.

public int getVisionCapacity();
     Retourne la capacité de vision de la bestiole.

public int getMovementCapacity();
     Retourne la capacité de déplacement de la bestiole.

public int getPosX();
     Retourne la position (en largeur) de la bestiole dans le monde.

public int getPosY();
     Retourne la position (en hauteur) de la bestiole dans le monde.

public int getDefenceMalus();
     Retourne le malus de défense inhérent à l'espèce de la bestiole.

public int getAttaqMalus();
     Retourne le malus d'attaque inhérent à l'espèce de la bestiole.

public int getFoodEatCapacity();
     Retourne la quantité de nourriture naturelle mangeable en un tour.

public int getCorpseEatCapacity();
     Retourne la quantité de chair mangeable en un tour.

public int getFoodMalus();
    Retourne le malus d'absorption de nourriture naturelle inhérent à l'espèce de la bestiole.

public int getCorpseMalus();
     Retourne le malus d'absorption de de cadavres inhérent à l'espèce de la bestiole.

public boolean sameBestiole(Bestiole b);
     Retourne true si la bestiole b est la bestiole à qui appartient l'instance de l'IA qui appelle la fonction.

public ServerType getServerType();
     Retourne le typt de serveur (arène ou domaine) dans lequel la bestiole évolue.

public Specie getSpecie();
     Retourne l'espèce de la bestiole (omnivore, carnivore ou herbivore)

public Component getParentComponent();
     Cette fonction retourne un composant de NeoNoe sur lequel rattacher d'eventuels boites de messages, de dialogue...

public boolean hasLastActionFailed();
     Cette fonction retourne true, si l'action précédente n'a pas été validée par le monde. Utile pour débuger!


     Les modificateurs : une IA peut forcer certaines caractéristiques de la bestiole, comme par exemple la capacité de vision, ou la possiblité que la vision evolue, etc. Dans ce cas, il est préférable de le signaler clairement dans la description de votre IA, et ces modifications doivent être faites à l'initialisation, JAMAIS pendant le jeu.

public setMovementCapacityEvoluable(boolean);
     Active / désactive l'evolution de la capacité de marche.

public setVisionCapacityEvoluable(boolean);
     Active / désactive l'evolution de la capacité de vision.

public setVisionCapacity(int cv);
     Modifie la capacité de vision.

public setMovementCapacity(int cm);
     Modifie la capacité de marche.

 

Les fonctions utiles de la classe BestioleList:

public Bestiole getBestioleAt(int index);
     Retourne la i-ème bestiole de la liste (la première est au rang 0).

public int size();
     Retourne le nombre de bestioles de la liste (0 si vide).

public Bestiole getByBestioleID(int bestioleID);
     Retourne la bestiole dont l'identifiant est 'bestioleID', null si elle n'est pas dans la liste.

 

L'IHM de configuration de l'IA:

     Vous pouvez programmer votre IHM de configuration comme vous le voulez, la seule condition est que le moteur de NeoNoe puisse appeler certaines fonctions précises. La classe java doit donc hériter de la classe 'iaengine.skeleton.BestioleIAConfig', qui contient une fonction appelée à la validation de la configuration, et une autre appelée pour initialiser les valeurs de champs de l'IHM en fonction de la bestiole à configurer.

public Object[] submit();
     Retourne un tableau contenant les parametres réglés par l'utilisateur. C'est ce tableau qui est passé en parametre à la fonction initIA.

public void show(BestioleIA ia);
     Fonction appelée à l'affichage de l'IHM. Pour une création d'IA : ia == null, sinon il faut remplir les champs de l'IHM avec les valeurs de l'IA passée en paramètre.

 

L'IA et l'évolution artificielle:

    L'IA étant un programme, ce n'est pas le code lui même qui évolue, mais ses variables.

    Pour indiquer qu'une variable est 'mutable' (c'est à dire que sa valeur peut changer par mutation), il faut ajouter une ligne dans la fonction 'addToMutationSystem(MutationSystem mutSys)' de la classe Java de l'IA, ce qui ajoutera cetet variable dans le système qui gère les mutations de NeoNoe. Par exemple, dans la classe BasicIA, le code suivant indique au système de mutation que la variable représentant le seuil en dessous duquel la bestiole cherche de la nourriture est mutable, indique sa valeur minimale, sa valeur maximale, et un texte la décrivant, pour un affichage éventuel de la mutation.

mutSys.add( new IAMutation(
    getClass().getDeclaredField("SEUIL_SEUL_CHERCHE_MANGER"),
// Champ de l'objet
    new Integer(0),
// valeur minimum
    new Integer(MAX_SEUIL_SEUL_CHERCHE_MANGER),
// valeur maximum
    "Seuil 'cherche_a_manger'"
// description pour affichage
));

Notes:

- Deux types de mutations existent : 'IAMutation', pour les mutations de l'IA, et 'FeatureMutation' pour celles de la bestiole elle même. N'ajoutez pas de 'FeatureMutation' au système de mutation d'une IA, ca ne servirait à rien.
- Le système de mutation ne gère pour l'instant que les entiers et les tableaux d'entiers... je sais, ce n'est pas pratique, et ça fait la 2564ème chose qu'il faut améliorer dans NeoNoe.
- Dans le cas d'un tableau de valeur, la mutation ne s'applique qu'à une seule valeur, pas à toutes.