Simulateur d'écosystème
Posté le 07/05/2014 21:54
J'expose ici un projet sur lequel je
m'arrache les cheveux planche depuis plusieurs semaines déjà : un add-in capable de simuler un écosystème, sous forme des sprites simples pour avoir une vue globale. 8)
Le panel d'éléments est très succinct, il y aura des petites plantes, des arbres, des herbivores et des carnivores. Il seront capables d'interagir entre eux en se courant après, en se bouffant et en se reproduisant.
La particularité de ce simulateur résidera dans l'introduction de l'évolution. Par exemple, si une population importante d'herbivores devient trop résistante, alors vous pourrez peut-être observer que certains carnivores arrivent à manger en adoptant un comportement charognard (et ce de façon indirecte, c'est-à-dire que le programme sera prévu pour que des mutations apparaissent, mais le fait que ces mutations se répandent si elles sont favorables ne sera dû qu'aux lois de la sélection naturelle).
Bon, j'espère que je ne vous ai pas déjà paumé, de toute façon les explications précédentes sont plus de l'ordre du détail technique (heureusement parce que sinon je n'aurai pas beaucoup de téléchargements
).
Bien sûr, pour que cet add-in reste ludique, l'utilisateur pourra agir à sa guise sur l'écosystème (je ne sais pas encore comment). Au final, ce ne sera pas vraiment un jeu mais plutôt une espèce de "bac à sable".
Pour l'anecdote, l'idée m'est venue en lisant un article de Science & vie sur la définition de la vie, qui abordait des éléments (chimiques, informatiques, physiques...) troublants, car très semblables à des formes de vie. Et ce qui a attiré mon attention est un programme (dont le nom m'échappe) d'un chercheur (dont le nom m'échappe aussi
) qui affichait des formes très simples, mais capables d'évoluer et de se parasiter entre elles.
Bref, ça m'avait plutôt fasciné, notamment le fait que le programme aboutit à des résultats non prévus car découlant du développement de l'I.A., et ça m'a donnait envie de faire pareil.
L'idée a germé et a aboutit à ce projet d’écosystème
Voilà, j'aurai préféré vous exposer ce projet plus tard car j'aurais bien aimé que vous ayez une démo digne de ce nom, mais il se trouve que je planche sur les I.A. et que j'ai actuellement un problème tenace que je ne parvient pas à résoudre moi-même
cf premier post
Citer : Posté le 07/05/2014 21:55 | #
Magnifique !
Citer : Posté le 07/05/2014 21:56 | # | Fichier joint
Le problème auquel je suis confronté est le suivant : chaque être est géré avec une structure, et cette structure contient un pointeur qui pointe sur la cible de l'animal (sa proie par exemple). Or il se trouve que ce pointeur prend souvent des valeurs anarchiques, aboutissant alors à des erreurs.
Malheureusement, je n'ai pas été capable de localiser la source de l'erreur, qui est donc cachée dans mon code. Je vous mets donc ce dernier ici (ainsi qu'en pièce jointe parce qu'il est long ), et je suis sincèrement désolé de ne pas pouvoir vous donner seulement un morceau qui rendraient le problème plus simple à résoudre
Le .c :
#include <stdlib.h>
#include "fxlib.h"
#include "string.h"
#include "MonochromeLib.h"
#include "usefull.h"
#include "Ecsystem.h"
#define CARTE(x,y) carte[(y-1)*largeur+x-1]
#define REPRODUCTION 400
#define FAIM(masse) 40*masse
#define VIE(masse) 2500+500*masse
#define RESISTANCE(masse) masse+1.0
int AddIn_main(int isAppli, unsigned short OptionNum)
{
srand(time_getTicks());
ML_clear_vram();
ML_clear_screen();
simulation();
}
void simulation()
{
short hauteur=5,largeur=10,vie;
short curseur_x=1,curseur_y=1;
Element *carte,*element,*element_apres;
short i,j;
Liste *liste;
char vitesse,resistance;
short faim;
int fps=10;
/*PrintV(1,1,sizeof(Element));
ML_display_vram();
while(1);*/
liste=initialisation();
carte=generation_map(hauteur,largeur,liste);
[red] //C'est ici que ça foire !
do
{
element=liste->premier;
while(element!=NULL)
{
//Gestion des Etres d'ici...
vitesse=element->masse;
resistance=RESISTANCE(element->masse);
faim=FAIM(element->masse);
element->vie--;
element_apres=element->suivant;
if(element->type > ARBRE)
{
if(element->etat != MORT)
{
if(element->cible!=NULL)if(element->cible->etat==INEXISTANT)ciblage(element,NULL);
objectif(element);
if(element->cible==NULL)recherche(element,carte,hauteur,largeur);
if(element->compteur>=vitesse)
{
if(element->cible!=NULL)
{
if(abs(element->cible->x - element->x)+abs(element->cible->y - element->cible->y)>1)deplacement(element,hauteur,largeur,carte);
else action(element,faim,liste,carte,hauteur,largeur);
}else deplacement(element,hauteur,largeur,carte);
element->compteur=0;
}else element->compteur++;
vie=1500+500*element->masse;
if(element->vie==3*(vie/4))element->etat=MOYEN;
if(element->vie==vie/4)element->etat=VIEUX;
if(!element->vie)
{
element->etat=MORT;
clear(element->x,element->y);
affichage(*element);
}
}
else if(!(element->vie%100))element->viande--;
if(!element->viande)
{
clear(element->x,element->y);
CARTE(element->x,element->y).type=0;
element->etat=INEXISTANT;
}
}
else reproduction_vegetal(liste,element,hauteur,largeur,carte);
if(element->etat==INEXISTANT && !element->nombre_predateurs)suppression(liste,element);
element=element_apres;
//...à là
if(IsKeyDown(KEY_CTRL_EXE))
{
for(i=1;i<=largeur;i++)for(j=1;j<=hauteur;j++)PrintV(7*i-6,8*j-7,CARTE(i,j).type);
ML_display_vram();
while(1);
}
if(IsKeyDown(KEY_CTRL_SHIFT))
{
while(IsKeyDown(KEY_CTRL_SHIFT));
while(IsKeyUp(KEY_CTRL_SHIFT));
while(IsKeyDown(KEY_CTRL_SHIFT));
}
}
ML_display_vram();
//setFps(fps);
}while(1);
while(1);[/red]
free(carte);
while(liste->premier!=NULL)suppression(liste,liste->premier);
}
Element *generation_map(short hauteur, short largeur,Liste *liste)
{
Element *carte=NULL;
short i,j;
carte=malloc(hauteur*largeur*sizeof(Element));
if(carte==NULL)erreur();
for(i=1;i<=largeur;i++)for(j=1;j<=hauteur;j++)CARTE(i,j).type=0;
creation_etre(liste->premier,largeur,hauteur,alea(1,3),carte,1,0,0);
for(i=1;i<(largeur*hauteur)/10;i++)insertion(liste,largeur,hauteur,carte,1,0,0,alea(1,3));
for(i=1;i<=largeur;i++)for(j=1;j<=hauteur;j++)affichage(CARTE(i,j));
ML_display_vram();
return carte;
}
int alea(int min,int max)
{
return (rand()%(max-min+1))+min;
}
void creation_etre(Element *etre,short largeur,short hauteur,Type type,Element *carte,char hasard,short x,short y)
{
char sante[]={2.0,3.0,4.0,5.0,6.0};
if(hasard)
{ do
{
etre->x=alea(1,largeur);
etre->y=alea(1,hauteur);
}while(CARTE(etre->x,etre->y).type);
}
else
{
etre->x=x;
etre->y=y;
}
etre->direction=alea(0,1);
etre->deplacement=alea(0,1);
etre->type=type;
etre->objectif=0;
etre->objectif_etat=0;
etre->action=RIEN;
etre->nombre_predateurs=0;
etre->cible=NULL;
if(etre->type > ARBRE)
{
etre->etat=JEUNE;
etre->masse=3;
}
else
{
etre->etat=INDEFINI;
etre->masse=5;
}
etre->sante=sante[etre->masse-1];
etre->compteur=0;
etre->viande=etre->masse+1;
etre->reproduction=REPRODUCTION;
etre->vie=VIE(etre->masse);
etre->faim=FAIM(etre->masse);
CARTE(etre->x,etre->y)=*etre;
}
void affichage(Element etre)
{
char *bmp,i;
char sprites_animaux[2][2][4][8]={
{
{{0,0,0,0,24,152,112,80},{0,0,0,0,140,124,120,72},{0,0,0,0,140,124,120,72},{0,0,0,0,72,120,124,140}}, //herbivore tourné vers la gauche
{{0,0,0,0,192,200,112,80},{0,0,0,0,196,248,120,72},{0,0,0,0,196,248,120,72},{0,0,0,0,72,120,248,196}} //herbivore tourné vers la droite
},{
{{0,0,0,0,152,120,144,72},{0,0,0,128,108,252,144,72},{0,0,0,128,108,252,144,72},{0,0,0,0,72,144,252,108}}, //carnivore tourné vers la gauche
{{0,0,0,0,200,240,72,144},{0,0,0,4,216,252,36,72},{0,0,0,4,216,252,36,72},{0,0,0,0,72,36,252,216}} //carnivore tourné vers la droite
}
};
char sprites_vegetaux[2][8]={{0,0,0,0,0,40,16,16},{0,24,60,126,60,24,24,60}};
if(etre.type > ARBRE)
{
bmp=sprites_animaux[etre.type-3][etre.direction][etre.etat-1];
}
else if(etre.type)bmp=sprites_vegetaux[etre.type-1];
if(etre.type)ML_bmp_or(bmp,7*etre.x-6,8*etre.y-7,7,8);
}
void clear(short x,short y)
{
char bmp[8]={1,1,1,1,1,1,1,1};
ML_bmp_8_and(bmp,7*x-6,8*y-7);
}
void deplacement(Element *etre,short hauteur,short largeur,Element *carte)
{
short x=etre->x,y=etre->y;
char i,move_x=0,move_y=0;
if(etre->cible!=NULL)
{
if(x > etre->cible->x)move_x=-1;
else move_x=1;
if(y > etre->cible->y)move_y=-1;
else move_y=1;
if(etre->action==FUITE)
{
move_x*=-1;
move_y*=-1;
}
if(etre->deplacement)
{
if(!CARTE(x,y+move_y).type && y+move_y>=1 && y+move_y<=hauteur)y+=move_y;
else if(!CARTE(x+move_x,y).type && x+move_x>=1 && x+move_x<=largeur)x+=move_x;
}
else
{
if(!CARTE(x+move_x,y).type && x+move_x>=1 && x+move_x<=largeur)x+=move_x;
else if(!CARTE(x,y+move_y).type && y+move_y>=1 && y+move_y<=hauteur)y+=move_y;
}
}
else if(!move(etre->x,etre->y,&x,&y,1,carte,hauteur,largeur))return;
if(etre->x!=x || etre->y!=y || etre->cible==NULL)
{
if(etre->x==x-1)etre->direction=1;
if(etre->x==x+1)etre->direction=0;
CARTE(etre->x,etre->y).type=0;
clear(etre->x,etre->y);
etre->x=x;
etre->y=y;
CARTE(x,y)=*etre;
affichage(*etre);
}
}
void reproduction_vegetal(Liste *liste,Element *etre,short hauteur, short largeur,Element *carte)
{
short x,y;
if(!move(etre->x,etre->y,&x,&y,1+(etre->type==2),carte,hauteur,largeur) && !etre->reproduction)
{
insertion(liste,largeur,hauteur,carte,0,x,y,etre->type);
affichage(*etre);
}
}
void recherche(Element *etre,Element *carte,short hauteur,short largeur)
{
char i,j,distance=0,Champ_de_vision=5;
short x=etre->x,y=etre->y;
int truc;
Element element;
for(i=-Champ_de_vision*(!etre->direction);i<=Champ_de_vision*etre->direction;i++)
{
for(j=-Champ_de_vision;j<=Champ_de_vision;j++)
{
if(j+y>=1 && j+y<=hauteur && i+x>=1 && i+x<=largeur && (j || i) && i+j<=Champ_de_vision)
{
element=CARTE(x+i,y+j);
if(element.type>0 && element.type<5 && (((element.type==etre->objectif && (element.etat==etre->objectif_etat || etre->objectif_etat==INDEFINI)) && i+j<distance) || (element.type==CARNIVORE && element.cible==etre && etre->type==HERBIVORE)))
{
ciblage(etre,&element);
etre->test=&element;
distance=i+j;
if(element.type==CARNIVORE && element.cible==etre && etre->type==HERBIVORE)
{
etre->action=FUITE;
return;
}
}
}
}
}
}
void objectif(Element *etre)
{
if(etre->reproduction && etre->etat==MOYEN)etre->reproduction--;
if(etre->etat!=JEUNE)etre->faim--;
if(etre->cible!=NULL)
{
if(etre->cible->cible!=etre && etre->action==FUITE)
{
etre->action=RIEN;
ciblage(etre,NULL);
}
else return;
}
if(!etre->reproduction)
{
etre->objectif=etre->type;
etre->objectif_etat=MOYEN;
etre->action=ACCOUPLEMENT;
}
if(etre->faim<=0)
{
if(etre->type==CARNIVORE)
{
etre->objectif=HERBIVORE;
etre->objectif_etat=INDEFINI;
}
else
{
etre->objectif=PETITE_PLANTE;
etre->objectif_etat=INDEFINI;
}
etre->action=MANGER;
}
}
void action(Element *etre,short faim,Liste *liste,Element *carte,short hauteur,short largeur)
{
int i;
if(etre->action==MANGER)
{
if(etre->cible->type > ARBRE && etre->cible->etat != MORT)etre->cible->sante--;
else
{
if(etre->cible->type > ARBRE)
{
if(!etre->cible->viande--)
{
clear(etre->cible->x,etre->cible->y);
CARTE(etre->cible->x,etre->cible->y).type=0;
etre->cible->etat=INEXISTANT;
ciblage(etre,NULL);
}
etre->faim+=10;
}
else
{
clear(etre->cible->x,etre->cible->y);
CARTE(etre->cible->x,etre->cible->y).type=0;
etre->cible->etat=INEXISTANT;
ciblage(etre,NULL);
etre->faim+=5;
}
if(etre->faim >= faim)
{
etre->faim=faim;
etre->objectif=0;
}
}
}
}
char move(short x1,short y1,short *x2,short *y2,char distance,Element *carte,short hauteur, short largeur)
{
char direction,i,d[]={0,0,0,0};
do
{
i=0;
*x2=x1;
*y2=y1;
direction=alea(1,4);
*x2+=distance*(direction==2);
*x2-=distance*(direction==4);
*y2+=distance*(direction==3);
*y2-=distance*(direction==1);
if(CARTE(*x2,*y2).type || *x2<1 || *x2>largeur || *y2<1 || *y2>hauteur)
{
d[direction-1]=1;
i=1;
}
if(d[0] && d[1] && d[2] && d[3])return 0;
}while(i);
return 1;
}
void erreur()
{
ML_clear_screen();
ML_clear_vram();
PrintXY(1,1,"Erreur, faites un reboot de la calto",0);
ML_display_vram();
while(1);
}
int abs(int nombre)
{
if(nombre<0)return -nombre;
else return nombre;
}
void ciblage(Element *etre,Element *cible)
{
if(etre->cible != NULL)etre->cible->nombre_predateurs--;
etre->cible=cible;
if(etre->cible != NULL)etre->cible->nombre_predateurs++;
}
//Gestion de la liste chaînée :
Liste *initialisation()
{
Liste *liste=NULL;
Element *element=NULL;
liste=malloc(sizeof(*liste));
element=malloc(sizeof(*element));
if (liste==NULL || element==NULL)erreur();
element->suivant=NULL;
element->precedent=NULL;
liste->premier=element;
liste->dernier=element;
liste->taille=1;
return liste;
}
void insertion(Liste *liste,short largeur,short hauteur,Element *carte,char hasard,short x,short y,Type type)
{
Element *nouveau=NULL;
nouveau=malloc(sizeof(*nouveau));
if (liste == NULL || nouveau == NULL)erreur();
creation_etre(nouveau,largeur,hauteur,type,carte,hasard,x,y);
liste->dernier->suivant=nouveau;
nouveau->precedent=liste->dernier;
liste->dernier=nouveau;
nouveau->suivant=NULL;
liste->taille++;
}
void suppression(Liste *liste,Element *element)
{
if (liste==NULL)erreur();
if(liste->premier!=element)element->precedent->suivant=element->suivant;
else liste->premier=element->suivant;
if(liste->dernier!=element)element->suivant->precedent=element->precedent;
else liste->dernier=element->precedent;
liste->taille--;
free(element);
}
#pragma section _BR_Size
unsigned long BR_Size;
#pragma section
#pragma section _TOP
int InitializeSystem(int isAppli, unsigned short OptionNum)
{
return INIT_ADDIN_APPLICATION(isAppli, OptionNum);
}
#pragma section
Mes énumérations et la structure en question :
typedef struct Liste Liste;
enum Type
{
PETITE_PLANTE=1,ARBRE=2,HERBIVORE=3,CARNIVORE=4
};
enum Etat
{
INDEFINI=0,JEUNE=1,MOYEN=2,VIEUX=3,MORT=4,INEXISTANT=5
};
enum Action
{
RIEN=0,REGROUPEMENT=1,ACCOUPLEMENT=2,MANGER=3,FUITE=4
};
typedef enum Type Type;
typedef enum Etat Etat;
typedef enum Action Action;
struct Liste
{
short taille;
Element *premier;
Element *dernier;
};
struct Element
{
enum Type type;
short vie;
short reproduction;
//Animaux uniquement :
short x;
short y;
char direction;
char deplacement;
signed short faim;
enum Etat etat;
char masse;
char sante;
char compteur;
char viande;
char nombre_predateurs;
enum Action action;
char objectif;
enum Etat objectif_etat;
Element *cible;
Element *test;
Element *suivant;
Element *precedent;
};
Citer : Posté le 07/05/2014 21:58 | #
Génial, c'est exactement le genre de projet que je rêvais de faire !
Enfin moi je pensais plus à des petites formes de vie avec lesquelles on peut un minimum interagir pour influencer leurs développement. Le but étant qu'a chaque partie on arrive à quelque chose de presque unique.
J'ai hâte de voir ça en tout cas
Citer : Posté le 07/05/2014 21:59 | #
On peut avoir les définitions de tes structures ?
Citer : Posté le 07/05/2014 22:01 | #
Bonne chance pour le projet !
Citer : Posté le 07/05/2014 22:03 | #
Merci à vous
@Lephenixnoir : la voici (dans le message au-dessus)
Édit : demandez-moi si vous voulez des précisions
Citer : Posté le 07/05/2014 22:09 | #
Signature !
Citer : Posté le 07/05/2014 22:21 | #
Je n'ai rien vu d'évident dans le code (mais il faudra regarder la gestion des pointeurs en détail).
N'es-tu tout de même pas arrivé à déterminer quelle fonction ou quelle partie de la gestion pouvait générer ces erreurs ?
Citer : Posté le 07/05/2014 22:33 | #
Je sais pas si c'est moi qui ai mal lu, mais je n'ai pas l'impression que tu initialise avec NULL tes pointeurs lors de la création d'un nouvel élément...
Il faudrait déjà commencer par là, même si c'est pas de là que viens le bug
Citer : Posté le 07/05/2014 23:08 | #
@Lephenixnoir : ce qui est sûr c'est que ça se passe pendant la boucle de gestion de l'écosystème (je vais la colorer en rouge, ce sera plus clair), mais je n'en sait pas plus.
J'ai d'abord cru que c'était un problème de la fonction "recherche" dont le rôle est de trouver une cible, mais je suis de plus en plus convaincu que c'est une autre fonction qui est concernée.
@Dark Storm : tous les pointeurs (sauf test) sont initialisés. C'est juste que "précédent" et "suivant" interviennent dans la gestion de la liste chaînée, donc je ne les initialise pas dans la fonction "creation_etre" mais plutôt dans la fonction "insertion".
Citer : Posté le 07/05/2014 23:09 | #
c'est normall un cf premier post sans lien ?
sinon quoi signature elto ?
-Mon Fall Down
-Mon jeu de mains
-Mon starwars
-Mon dessinatout
-Mon niaiseux version 2.0
-Mon niaiseux version 3.0
-Inferno
-Mon super labyrinthe (en cours)
-Mon call of duty en 3D
-Casion (avec Az)
Citer : Posté le 07/05/2014 23:13 | #
Oui c'est normal : dans le post principal je parlerai de l'avancée du projet, donc je ne veux pas l'encombrer avec quelque chose d'aussi secondaire et éphémère qu'une demande d'aide
Citer : Posté le 07/05/2014 23:14 | #
Signature = Je le met dans ma signature xD
Citer : Posté le 08/05/2014 00:02 | #
Une sorte de LIFE sur calto? J'en ai déjà mais il ne marchais pas . J'adore l'idée! J'ai hâte d'essayer
Ajouté le 08/05/2014 à 03:15 :
Bah signature aussi alors
Citer : Posté le 08/05/2014 06:44 | #
Je peux savoir à quoi sert le while(1); vide, à la dernière ligne rouge ?
Un peu plus haut, tu définis que si !element->viande, element disparaît. Or l'appel à la fonction suppression est soumis à deux à conditions:
Ce qui veut dire que s'il reste des prédateurs, l'élément ne disparaît pas, donc ces prédateurs pointent sur un élément vide. Ceci ne sera corrigé qu'au tour suivant, donc on a des résidus d'êtres inexistants d'un tour sur l'autre.
Mais surtout, le ciblage(element,NULL) ne s'effectue que si element->vie!=MORT. Or si un animal "meurt" alors qu'il lui reste des prédateurs, on ne passe pas par la fonction supprimer(), donc on a juste un appel à clear() et l'affection de INEXISTANT à son élément vie.
Donc encore une fois, une "fuite" de mémoire jusqu'au tour suivant.
Citer : Posté le 08/05/2014 10:21 | #
Décidément, cette communauté m'impressionnera toujours
Bon courage Positon !
Vitesse des fonctions en Basic Casio | 7 days CPC | Casio Universal Wiki | Tutoriel Basic Casio
>>> Give me a click Brother <<< >>> Teste mon générateur de mots nouveaux <<<
>>> Random Youtube Video <<<
Citer : Posté le 08/05/2014 10:40 | #
Merci Totoyo
En fait l'ajout de l'état INEXISTANT a justement été fait dans le but d'éviter que d'éventuels prédateurs pointent sur un élément vide. Lorsqu'un animal est mort et entièrement décomposé, il ne sert à rien mais si je le supprime, c'est justement ça qui va faire en sorte que les prédateurs pointeront sur une structure inexistante.
Par conséquent, une condition en début de boucle initialise le pointeur cible de chaque être à NULL si element->cible->état==INEXISTANT . Ainsi, l'être mort et décomposé n'est supprimé que lorsque tous ses prédateurs ont abandonné leur cible
D'ailleurs je ne sais pas où tu as vu un "if(element->vie != MORT)ciblage(element,NULL);", etant donné que la composante vie est un short et non une énumération
Quant au while(1), c'est juste quelque chose que j'avais placé au tout début, pour me rassurer...histoire de d'abord tester l'arrêt de la simulation avant la déletion de l'ecosystème (enfin bon, c'était juste par rigueur et dans le but de ne pas brûler les étapes, mais en réalité il ne sert à rien ).
Citer : Posté le 08/05/2014 10:47 | #
Oui, c'est element->etat != MORT.
Ce que je veux dire, c'est que les prédateurs n'abandonnent leurs proies que s'ils ne sont pas morts au tour suivant. Et si ils meurent au même tour ?
Citer : Posté le 08/05/2014 11:04 | #
C'est pas faux, en fait il suffit d'initialiser systématiquement le pointeur à NULL en cas de mort
Citer : Posté le 08/05/2014 11:07 | #
Et surtout, je ne vois pas pourquoi tu ne réorganises pas ta boucle de traitement pour que l'element mort soit détruit avant le tour suivant.