Allegro 5

Programmation de jeux en C ou C++


précédentsommairesuivant

VIII. Croquis de fourmis

VIII-A. Introduction

L'idée ici est une étude de cas qui peut être très utile dans différents domaines comme la vie artificielle et bien entendu les jeux. L'objectif est de gérer un ensemble d'entités qui se déplacent dans un espace. Nous considérons que ce sont des fourmis ; pour autant nous n'avons pas cherché ici à bâtir une histoire dans laquelle les fourmis partent à la conquête d'un monde. Il n'y a pas de décor et pas d'interaction avec un décor (la question des décors est abordée dans un autre chapitre). Il s'agit juste d'un croquis préliminaire qui peut servir de base à des enrichissements.

VIII-B. Fourmi seule

Qui est notre fourmi ? Comment est-elle ? Que fait-elle ? Ce sont les premières questions à se poser avant de commencer à coder.

Notre fourmi de base est quelque part dans la fenêtre ou l'écran. Elle peut se déplacer. Elle a une forme, une taille, éventuellement une couleur ou une image. Notre idée est de la représenter sous la forme d'un petit carré coloré. Mais pour améliorer la qualité des déplacements et profiter de certaines accélérations graphiques, ce petit carré est une image bitmap. Le cas échéant ça facilitera également le passage à une représentation visuelle plus travaillée à partir d'images et d'animations (les animations sont présentées dans un chapitre plus loinAnimations, sprites).

Nous proposons deux versions pour l'écriture du programme. Dans la première, une fourmi est de type valeur (ce n'est pas un pointeur), dans la seconde la fourmi est de type référence (c'est un pointeur). L'algorithme est exactement le même et les détails donnés ci-dessous s'appuient sur une fourmi de type valeur. Une seconde version de code, avec une fourmi de type référence, est ensuite présentée.

VIII-B-1. Structure de données d'une fourmi

À partir de notre portrait-robot de fourmi, nous pouvons déduire une structure de données :

  • la position (x,y) et le déplacement (dx, dy) peuvent être stockés avec des valeurs entières ou flottantes : int ou float .
  • la taille est en une valeur entière : int .
  • la couleur est une structure ALLEGRO_COLOR et l'image est un pointeur ALLEGRO_BITMAP* .
  • le tout est réuni dans un type de structure « fourmi » de la façon suivante :
 
Sélectionnez
typedef struct {
    int x,y,dx,dy; // déplacement
    int tx,ty; // taille
    ALLEGRO_COLOR color;
    ALLEGRO_BITMAP*image;
}fourmi;

VIII-B-2. Initialisation

Maintenant que nous avons un modèle de fourmi, la première chose est de pouvoir construire des fourmis. Pour ce faire, nous avons besoin d'un constructeur à savoir d'une fonction d'initialisation. L'idée pour commencer est d'initialiser une fourmi avec des valeurs aléatoires. Nous avons ainsi la fonction suivante :

 
Sélectionnez
fourmi init_alea_fourmi()
{
    fourmi f;
    f.tx=10;
    f.ty=10;
    f.x=rand()%(SCREENX-f.tx);
    f.y=rand()%(SCREENY-f.ty);
    f.dx=rand()%11-5;
    f.dy=rand()%11-5;
    f.color=COLORALEA;
    f.image=al_create_bitmap(f.tx, f.ty);
    if(!f.image)
        erreur("f.image=al_create_bitmap(f.tx, f.ty)");
    // obligatoire pour pouvoir dessiner dans une bitmap mémoire
    al_set_target_bitmap(f.image);
    al_clear_to_color(f.color);
    return f;
}

La taille, 10 pixels, est fixe et sera la même pour toutes les fourmis.

La position, le déplacement et la couleur sont aléatoires. La valeur en pixels d'un pas de déplacement est comprise entre -5 et 5 pixels.

Pour la couleur la macro COLORALEA simplifie l'écriture de la façon suivante :

 
Sélectionnez
#define COLORALEA al_map_rgb(rand()%256, rand()%256, rand()%256)

Chaque fourmi possède une image et elle est représentée par un carré de couleur. La taille est définie avec les champs tx et ty et la couleur avec le champ couleur. Rappelons que pour dessiner dans une bitmap il faut tout d'abord sélectionner la bitmap comme bitmap cible pour les opérations de dessin avec la fonction al_set_target_bitmap() . Pour pouvoir afficher à l'écran, il faut ensuite appeler la fonction al_set_target_backbuffer() .

VIII-B-3. Affichage

Afficher une fourmi est très simple, la fonction d'affichage prend en paramètre une fourmi à afficher et appelle la fonction d'affichage d'image al_draw_bitmap() pour afficher l'image de la fourmi. C'est la fonction :

 
Sélectionnez
void affiche_fourmi(fourmi f)
{
    al_draw_bitmap(f.image,f.x, f.y, 0);
}

VIII-B-4. Mouvement

La fourmi se déplace dans l'écran et reste dans l'écran. Si elle cogne un bord, elle part dans l'autre sens tout en modifiant la vitesse de son pas de déplacement. La fonction prend en paramètre la fourmi à déplacer. Mais attention, le paramètre est une variable locale à la fonction et il s'agit au moment de l'appel d'une copie de l'original. C'est pourquoi après avoir modifié cette copie, la fonction retourne la copie modifiée afin qu'elle puisse être affectée à l'original et que l'original enregistre de cette façon les modifications portées sur la copie. C'est là que l'utilisation de pointeurs est pratique afin de pouvoir transformer une entrée de la fonction en sortie avec un passage par référence (voir la version pointeur du programmeCode complet version référence).

 
Sélectionnez
fourmi move_fourmi(fourmi f)
{
    f.x+=f.dx;
    if(f.x<0){
        f.x=0;
        f.dx=1+rand()%5;
    }
    if(f.x+f.tx >= SCREENX){
        f.x=SCREENX-f.tx;
        f.dx= (1+rand()%5)*-1;
    }
    f.y+=f.dy;
    if(f.y<0){
        f.y=0;
        f.dy=1+rand()%5;
    }
    if(f.y+f.ty >= SCREENY){
        f.y=SCREENY-f.ty;
        f.dy= (1+rand()%5)*-1;
    }
    return f;
}

VIII-B-5. Libération mémoire

Dans un langage comme le C ou le C++, le programmeur doit désallouer tout ce qu'il a alloué. C'est ce qui motive la fonction de nettoyage de la fourmi. Cette fonction désalloue la bitmap allouée pour la fourmi passée en paramètre. En l'occurrence, il n'y a pas besoin de retour parce que l'adresse mémoire de l'image à désallouer, même portée par une copie, reste la même.

 
Sélectionnez
void nettoie_fourmi(fourmi f)
{
    al_destroy_bitmap(f.image);
}

VIII-B-6. Code complet

Il ne reste plus qu'à organiser l'ensemble du code. La fourmi est initialisée avant la boucle d'événements et de l'action. Elle avance à chaque événement du minuteur. Et elle est réaffichée à chaque événement, quel qu'il soit.

Une fourmi seule
Sélectionnez
#include <allegro5\allegro.h>
#include <allegro5\allegro_native_dialog.h>
#include <allegro5\allegro_primitives.h>

// les define
#define COLORALEA    al_map_rgb(    rand()%256,\
                                        rand()%256,\
                                        rand()%256)
#define NOIR        al_map_rgb(0,0,0)
// les constantes
const int SCREENX=800;
const int SCREENY=600;

// les types
typedef struct {

    int x,y,dx,dy;            // déplacement
    int tx,ty;                // taille
    ALLEGRO_COLOR color;
    ALLEGRO_BITMAP*image;    

}fourmi;

// les fonctions
fourmi    init_alea_fourmi        (void);
fourmi    move_fourmi                (fourmi f);
void        affiche_fourmi            (fourmi f);
void        nettoie_fourmi            (fourmi f);
void        erreur                    (const char*txt);

/***********************************************************
***********************************************************/
int main()
{
ALLEGRO_DISPLAY*display;
ALLEGRO_EVENT_QUEUE*queue;
ALLEGRO_TIMER*timer;
fourmi f;
int fin=0;

    srand(time(NULL));

    if(!al_init())
        erreur("al_init()");

    display=al_create_display(SCREENX,SCREENY);
    if(!display)
        erreur("al_create_display");

    if(!al_install_keyboard())
        erreur("al_install_keyboard()");

    if(!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");

    queue=al_create_event_queue();
    if(!queue)
        erreur("al_create_event_queue()");

    timer=al_create_timer(1.0/60);

    al_register_event_source(queue, al_get_display_event_source(display));
    al_register_event_source(queue, al_get_keyboard_event_source());
    al_register_event_source(queue, al_get_timer_event_source(timer));

    // initialisation fourmi
    f = init_alea_fourmi();
    // retour à l'affichage écran
    al_set_target_backbuffer(display);

    al_start_timer(timer);
    while(!fin){
        
        ALLEGRO_EVENT event={0}; 
        al_wait_for_event(queue,&event);

        // analyse évènements :

        if(event.type == ALLEGRO_EVENT_KEY_DOWN){ // clavier
            switch(event.keyboard.keycode){
                
                case ALLEGRO_KEY_ESCAPE:    fin=1;    break;
            }
        }
        else if(event.type==ALLEGRO_EVENT_DISPLAY_CLOSE)// fenêtre
            fin=1;
        else if(event.type==ALLEGRO_EVENT_TIMER){// timer

            f = move_fourmi(f);

        }

        // affichage
        al_clear_to_color(NOIR); // efface écran
        affiche_fourmi(f);
        al_flip_display();        // voir 

    }
    nettoie_fourmi(f);
    al_destroy_display(display);
    al_destroy_event_queue(queue);
    return 0;
}
/***********************************************************
initialisation
***********************************************************/
fourmi init_alea_fourmi()
{
fourmi f;

    f.tx=10;
    f.ty=10;
    f.x=rand()%(SCREENX-f.tx);
    f.y=rand()%(SCREENY-f.ty);
    f.dx=rand()%11-5;
    f.dy=rand()%11-5;
    f.color=COLORALEA;
    f.image=al_create_bitmap(f.tx, f.ty);
    if(!f.image)
        erreur("f.image=al_create_bitmap(f.tx, f.ty)");
    
    // obligatoire pour pouvoir dessiner dans une bitmap mémoire
    al_set_target_bitmap(f.image);
    al_clear_to_color(f.color);
    return f;
}

/***********************************************************
déplacement
***********************************************************/
fourmi move_fourmi(fourmi f)
{
    f.x+=f.dx;
    if(f.x<0){
        f.x=0;
        f.dx=1+rand()%5;
    }
    if(f.x+f.tx >= SCREENX){
        f.x=SCREENX-f.tx;
        f.dx= (1+rand()%5)*-1;
    }
    f.y+=f.dy;
    if(f.y<0){
        f.y=0;
        f.dy=1+rand()%5;
    }
    if(f.y+f.ty >= SCREENY){
        f.y=SCREENY-f.ty;
        f.dy= (1+rand()%5)*-1;
    }
    return f;
}
/***********************************************************
affichage
***********************************************************/
void affiche_fourmi(fourmi f)
{
    al_draw_bitmap(f.image,f.x, f.y, 0);
}
/***********************************************************
nettoyage 
***********************************************************/
void nettoie_fourmi(fourmi f)
{
    al_destroy_bitmap(f.image);
}
/***********************************************************
outils
***********************************************************/
void erreur(const char*txt)
{
ALLEGRO_DISPLAY*d;
    d=al_is_system_installed()?al_get_current_display():NULL;
    al_show_native_message_box(d,"ERREUR",txt,NULL,NULL,0);
    exit(EXIT_FAILURE);
}
/***********************************************************
***********************************************************/

VIII-B-7. Code complet version référence

Jusqu'ici la fourmi était manipulée à partir d'un type valeur, à savoir une structure en dur. Dans cette seconde version, la fourmi est un pointeur alloué dynamiquement et la fourmi est toujours manipulée via ce pointeur. C'est ce que l'on appelle un type référence, c'est toujours une référence à l'adresse mémoire de la fourmi qui est utilisée.

L'algorithme est le même. Seule change l'écriture pour les paramètres de fonction qui deviennent des pointeurs et il y a un gros avantage : plus besoin de valeur de retour (sauf pour la fonction d'initialisation). En effet comme ce qui est passé à la fonction est une adresse mémoire, il est possible pour la fonction d'écrire à cette adresse mémoire et ainsi les entrées de la fonction peuvent être transformées en sortie. De plus, en utilisant des pointeurs, les structures passées en paramètre n'ont plus besoin d'être copiées. Ainsi, le programme ne perdra plus de temps à copier nos données et sera donc plus rapide.

Pour ceux et celles qui ne seraient pas familiers de l'utilisation des pointeurs, l'accès aux champs d'une structure via un pointeur se fait avec l'opérateur flèche ->. Par exemple :

 
Sélectionnez
fourmi f ;
fourmi*ptr=&f ;
ptr->x=0 ;
ptr->y=0 ;
etc.

Voici le code complet de la seconde version avec une fourmi de type référence.

Une fourmi seule (version référence)
Sélectionnez
#include <allegro5\allegro.h>
#include <allegro5\allegro_native_dialog.h>
#include <allegro5\allegro_primitives.h>

// les define
#define COLORALEA    al_map_rgb(    rand()%256,\
                                        rand()%256,\
                                        rand()%256)
#define NOIR        al_map_rgb(0,0,0)
// les constantes
const int SCREENX=800;
const int SCREENY=600;

// les types
typedef struct {

    int x,y,dx,dy;            // déplacement
    int tx,ty;                // taille
    ALLEGRO_COLOR color;
    ALLEGRO_BITMAP*image;    

}fourmi;

// les fonctions
fourmi*    init_alea_fourmi        (void);
void            move_fourmi                (fourmi*f);
void            affiche_fourmi            (fourmi*f);
void            destroy_fourmi            (fourmi**f);
void            erreur                    (const char*txt);

/***********************************************************
***********************************************************/
int main()
{
ALLEGRO_DISPLAY*display;
ALLEGRO_EVENT_QUEUE*queue;
ALLEGRO_TIMER*timer;
fourmi*f;

int fin=0;

    srand(time(NULL));

    if(!al_init())
        erreur("al_init()");

    display=al_create_display(SCREENX,SCREENY);
    if(!display)
        erreur("al_create_display");

    // obligatoire pour avoir le clavier
    if(!al_install_keyboard())
        erreur("al_install_keyboard()");

    // pour dessiner
    if(!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");

    // la file des événements
    queue=al_create_event_queue();
    if(!queue)
        erreur("al_create_event_queue()");

    // le topage
    timer=al_create_timer(1.0/60);

    // sélectionner les événements à considérer
    al_register_event_source(queue, al_get_display_event_source(display));
    al_register_event_source(queue, al_get_keyboard_event_source());
    al_register_event_source(queue, al_get_timer_event_source(timer));

    // initialisation fourmi
    f = init_alea_fourmi();
    // retour à l'affichage écran
    al_set_target_backbuffer(display);

    al_start_timer(timer);
    while(!fin){
        
        ALLEGRO_EVENT event={0}; 
        al_wait_for_event(queue,&event);

        // analyse évènements :

        if(event.type == ALLEGRO_EVENT_KEY_DOWN){ // clavier
            switch(event.keyboard.keycode){
                
                case ALLEGRO_KEY_ESCAPE:    fin=1;    break;
            }
        }
        else if(event.type==ALLEGRO_EVENT_DISPLAY_CLOSE)// fenêtre
            fin=1;
        else if(event.type==ALLEGRO_EVENT_TIMER){// timer

            move_fourmi(f); // lecture-écriture

        }

        // affichage
        al_clear_to_color(NOIR); // efface écran
        affiche_fourmi(f); // lecture
        al_flip_display();        // voir 

    }
    // désallocation de la fourmi et de son image
    destroy_fourmi(&f);

    al_destroy_display(display);
    al_destroy_event_queue(queue);
    return 0;
}
/***********************************************************
initialisation
***********************************************************/
fourmi* init_alea_fourmi()
{
    // la fourmi doit faire l'objet d'une allocation dynamique
    fourmi* f=(fourmi*)malloc(sizeof(fourmi));

    f->tx=10;
    f->ty = 10;
    f->x = rand() % (SCREENX - f->tx);
    f->y = rand() % (SCREENY - f->ty);
    f->dx = rand() % 11 - 5;
    f->dy = rand() % 11 - 5;
    f->color = COLORALEA;
    f->image = al_create_bitmap(f->tx, f->ty);
    if (!f->image)
        erreur("f.image=al_create_bitmap(f.tx, f.ty)");
    
    // obligatoire pour pouvoir dessiner dans une bitmap mémoire
    al_set_target_bitmap(f->image);
    al_clear_to_color(f->color);
    return f;
}

/***********************************************************
déplacement
***********************************************************/
void move_fourmi(fourmi*f)
{
    // opérateur ->
    
    f->x+=f->dx;
    if(f->x<0){
        f->x=0;
        f->dx=1+rand()%5;
    }
    if(f->x+f->tx >= SCREENX){
        f->x=SCREENX-f->tx;
        f->dx= (1+rand()%5)*-1;
    }
    f->y+=f->dy;
    if(f->y<0){
        f->y=0;
        f->dy=1+rand()%5;
    }
    if(f->y+f->ty >= SCREENY){
        f->y=SCREENY-f->ty;
        f->dy= (1+rand()%5)*-1;
    }
}
/***********************************************************
affichage
***********************************************************/
void affiche_fourmi(fourmi*f)
{
    // opérateur ->
    
    al_draw_bitmap(f->image,f->x, f->y, 0);
}
/***********************************************************
nettoyage : le pointeur fourmi est lui-même passé par référence
afin de pouvoir modifier sa valeur dans la fonction. C'est ce qui
explique la double étoile du paramètre : il s'agit d'un pointeur de
pointeur. L'opérateur d'accès est l'étoile ainsi *f vaut ici
l'adresse mémoire du pointeur passé par référence au moment de
l'appel (la fourmi dans le main).
Ce n'est pas obligatoire de faire ainsi ici mais c'est pour pouvoir
mettre à NULL le pointeur de la fourmi dans le contexte d'appel.
***********************************************************/
void destroy_fourmi(fourmi**f)
{
    al_destroy_bitmap((*f)->image);
    free(*f);
    *f = NULL;
}
/***********************************************************
tools
***********************************************************/
void erreur(const char*txt)
{
ALLEGRO_DISPLAY*d;
    d=al_is_system_installed()?al_get_current_display():NULL;
    al_show_native_message_box(d,"ERREUR",txt,NULL,NULL,0);
    exit(EXIT_FAILURE);
}
/***********************************************************
***********************************************************/

VIII-C. Colonie de fourmis

C'est assez simple de passer de la fourmi à une colonie de fourmis : il suffit d'avoir un tableau de fourmis.

VIII-C-1. Structure de données d'une colonie

Juste un tableau de fourmis. Sa taille est définie par une directive macro processeur :

 
Sélectionnez
#define MAXFOURMI 200
(...)
fourmi f[MAXFOURMI];

VIII-C-2. Initialisation

Pour chaque fourmi, appeler la fonction d'initialisation d'une fourmi :

 
Sélectionnez
for (i=0;i<MAXFOURMI;i++)
    f[i]= init_color_fourmi(ROUGE);

Dans la perspective ensuite d'avoir plusieurs colonies, nous avons défini une nouvelle couleur :

 
Sélectionnez
#define ROUGE al_map_rgb(128+rand()%128,0,0)

VIII-C-3. Affichage

Comme pour l'initialisation, il suffit d'appeler la fonction d'affichage pour chaque fourmi :

 
Sélectionnez
for(i=0;i<MAXFOURMI;i++)
    affiche_fourmi(f[i]);

VIII-C-4. Mouvement

Idem pour le mouvement : appeler la fonction mouvement pour chaque fourmi :

 
Sélectionnez
for(i=0;i<MAXFOURMI;i++)// mouvement
    f[i] = move_fourmi(f[i]);

VIII-C-5. Mise à jour du code

Il y a très peu de modifications à faire et elles sont toutes faites dans le main() indiquées en gras. C'est pourquoi nous redonnons uniquement le main, les autres fonctions sont exactement les mêmes.

Une colonie de fourmis
Sélectionnez
#include <allegro5\allegro.h>
#include <allegro5\allegro_native_dialog.h>
#include <allegro5\allegro_primitives.h>

// les define
#define COLORALEA    al_map_rgb(rand()%256,rand()%256,rand()%256)
#define NOIR        al_map_rgb(0,0,0)
#define ROUGE al_map_rgb(128+rand()%128,0,0)
#define MAXFOURMI 2000

// les constantes
const int SCREENX=800;
const int SCREENY=600;

// les types
typedef struct {

    int x,y,dx,dy;            // déplacement
    int tx,ty;                // taille
    ALLEGRO_COLOR color;
    ALLEGRO_BITMAP*image;    

}fourmi;

// les fonctions
fourmi init_color_fourmi (ALLEGRO_COLOR color);
fourmi    move_fourmi                (fourmi f);
void        affiche_fourmi            (fourmi f);
void nettoie_fourmi (fourmi f);
void        erreur                    (const char*txt);

/***********************************************************
***********************************************************/
int main()
{
ALLEGRO_DISPLAY*display;
ALLEGRO_EVENT_QUEUE*queue;
ALLEGRO_TIMER*timer;

fourmi f[MAXFOURMI]; // une colonie de fourmis, un ensemble de fourmis

int i,fin=0;

    srand(time(NULL));

    if(!al_init())
        erreur("al_init()");

    display=al_create_display(SCREENX,SCREENY);
    if(!display)
        erreur("al_create_display");

    // obligatoire pour avoir le clavier
    if(!al_install_keyboard())
        erreur("al_install_keyboard()");

    // pour dessiner
    if(!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");

    // la file des événements
    queue=al_create_event_queue();
    if(!queue)
        erreur("al_create_event_queue()");

    // le topage
    timer=al_create_timer(1.0/60);

    // sélectionner les événements à considérer
    al_register_event_source(queue, al_get_display_event_source(display));
    al_register_event_source(queue, al_get_keyboard_event_source());
    al_register_event_source(queue, al_get_timer_event_source(timer));

    // initialisation fourmi
 for (i=0;i<MAXFOURMI;i++)
        f[i]= init_color_fourmi(ROUGE);

    // retour à l'affichage écran
    al_set_target_backbuffer(display);

    al_start_timer(timer);
    while(!fin){
        
        ALLEGRO_EVENT event={0}; 
        al_wait_for_event(queue,&event);

        // analyse évènements :

        if(event.type == ALLEGRO_EVENT_KEY_DOWN){ // clavier
            switch(event.keyboard.keycode){
                case ALLEGRO_KEY_ESCAPE:    fin=1;    break;
            }
        }
        else if(event.type==ALLEGRO_EVENT_DISPLAY_CLOSE)// fenêtre
            fin=1;
        else if(event.type==ALLEGRO_EVENT_TIMER){// timer

            for(i=0;i<MAXFOURMI;i++)// mouvement des fourmis
                f[i] = move_fourmi(f[i]);

        }

        // affichage
        al_clear_to_color(NOIR); // efface écran

 for(i=0;i<MAXFOURMI;i++)
            affiche_fourmi(f[i]);

        al_flip_display();        // voir 

    }

    // libération mémoire de chaque fourmi (image)
    for(i=0;i<MAXFOURMI;i++)
        nettoie_fourmi(f[i]);

    al_destroy_display(display);
    al_destroy_event_queue(queue);
    return 0;
}
/***********************************************************
***********************************************************/

VIII-C-6. Mise à jour du code, version référence

De même que dans l'autre version, les fonctions sont exactement les mêmes et toutes les modifications sont uniquement dans le main() , les voici :

Une colonie de fourmis (version référence)
Sélectionnez
#include <allegro5\allegro.h>
#include <allegro5\allegro_native_dialog.h>
#include <allegro5\allegro_primitives.h>

// les define
#define COLORALEA    al_map_rgb(rand()%256,rand()%256,rand()%256)
#define NOIR        al_map_rgb(0,0,0)
#define ROUGE al_map_rgb(128+rand()%128,0,0)
#define MAXFOURMI 2000

// les constantes
const int SCREENX = 800;
const int SCREENY = 600;

// les types
typedef struct {

    int x, y, dx, dy;            // déplacement
    int tx, ty;                // taille
    ALLEGRO_COLOR color;
    ALLEGRO_BITMAP*image;

}fourmi;

// les fonctions
fourmi* init_color_fourmi(ALLEGRO_COLOR color);
void    move_fourmi(fourmi* f);
void        affiche_fourmi(fourmi*f);
void destroy_fourmi(fourmi**f);
void        erreur(const char*txt);

/***********************************************************
***********************************************************/
int main()
{
    ALLEGRO_DISPLAY*display;
    ALLEGRO_EVENT_QUEUE*queue;
    ALLEGRO_TIMER*timer;
    // un ensemble, une colonie de fourmis est
    // un tableau de pointeurs
    fourmi* f[MAXFOURMI];

    int i, fin = 0;

    srand(time(NULL));

    if (!al_init())
        erreur("al_init()");

    display = al_create_display(SCREENX, SCREENY);
    if (!display)
        erreur("al_create_display");

    // obligatoire pour avoir le clavier
    if (!al_install_keyboard())
        erreur("al_install_keyboard()");

    // pour dessiner
    if (!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");

    // la file des événements
    queue = al_create_event_queue();
    if (!queue)
        erreur("al_create_event_queue()");

    // le topage
    timer = al_create_timer(1.0 / 60);

    // sélectionner les événements à considérer
    al_register_event_source(queue, al_get_display_event_source(display));
    al_register_event_source(queue, al_get_keyboard_event_source());
    al_register_event_source(queue, al_get_timer_event_source(timer));

    // initialisation de la colonie
    for (i = 0; i<MAXFOURMI; i++)
        f[i] = init_color_fourmi(ROUGE);

    // retour à l'affichage écran
    al_set_target_backbuffer(display);

    al_start_timer(timer);
    while (!fin){

        ALLEGRO_EVENT event = { 0 };
        al_wait_for_event(queue, &event);

        // analyse évènements :

        if (event.type == ALLEGRO_EVENT_KEY_DOWN){ // clavier
            switch (event.keyboard.keycode){

            case ALLEGRO_KEY_ESCAPE:    fin = 1;    break;
            }
        }
        else if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)// fenêtre
            fin = 1;
        else if (event.type == ALLEGRO_EVENT_TIMER){// timer

            // mouvement des fourmis de la colonie
            for (i = 0; i<MAXFOURMI; i++)
                move_fourmi(f[i]);

        }

        // affichage
        al_clear_to_color(NOIR); // efface écran

        for (i = 0; i<MAXFOURMI; i++)
            affiche_fourmi(f[i]);

        al_flip_display();        // voir 

    }

    // désallocation de chaque fourmi
    for (i = 0; i<MAXFOURMI; i++)
        destroy_fourmi(f+i); //équivalent &f[i]

    al_destroy_display(display);
    al_destroy_event_queue(queue);
    return 0;
}
/***********************************************************
***********************************************************/

VIII-D. Colonie et collisions

Même programme mais avec en plus une gestion des collisions entre les fourmis. Les collisions de base font l'objet d'une section détaillée dans le chapitre Modèle de jeu simpleModèle de jeu simple avec la création de jeux. Nous allons utiliser ici la technique de l'intersection de rectangles.

VIII-D-1. Collision entre deux fourmis

Le principe est simple : si une fourmi se cogne dans une autre, elle part dans l'autre sens. Pour repérer la collision entre deux fourmis, nous allons regarder s'il y a une superposition entre les images des deux fourmis.

Ainsi nous avons besoin d'une fonction qui prend en paramètre deux fourmis et si la première rencontre la seconde, la première change de direction. Il y a juste un test à faire sur les coordonnées des deux fourmis (si besoin vous trouverez les explications de ce test dans la section Collisions du chapitre Modèle de jeu simpleCollisions).

S'il y a collision, la première fourmi est modifiée, donc il faut retourner la fourmi modifiée.

 
Sélectionnez
fourmi collision_fourmi(fourmi f1, fourmi f2)
{
    if (f1.x < f2.x+f2.tx && f1.x+f1.tx > f2.x &&
            f1.y < f2.y+f2.ty && f1.y+f1.ty > f2.y ){
        f1.dx*=-1;
        f1.dy*=-1;
    }
    return f1;
}

VIII-D-2. Collisions dans la colonie

Le repérage des collisions peut se faire au moment du mouvement. Pour chaque fourmi qui avance, il convient de regarder toutes les autres et vérifier pour chacune s'il y a ou non une collision. Si oui, le sens de la fourmi considérée est inversé. Ce qui donne :

 
Sélectionnez
for(i=0;i<MAXFOURMI;i++){
    f[i] = move_fourmi(f[i]); //mouvement d'une fourmi
    for (j=0; j<MAXFOURMI; j++){ //regarder toutes les autres
        if(i==j)
            continue;
        f[i] = collision_fourmi(f[i], f[j]); //pour voir si collision
    }
}

Évidemment il faut éviter le cas où la fourmi regarde si elle est en collision avec elle-même. Le résultat est toujours vrai et alors elle changera de sens à chaque fois. C'est l'objectif de l'instruction continue qui, dans ce cas, saute à l'itération suivante.

VIII-D-3. Mise à jour du code

Il y a juste à rajouter la fonction de collision et son appel décrit ci-dessus. Voici le main complet et la fonction ensuite :

Colonie et collisions
Sélectionnez
#include <allegro5\allegro.h>
#include <allegro5\allegro_native_dialog.h>
#include <allegro5\allegro_primitives.h>

// les define
#define COLORALEA    al_map_rgb(rand()%256,rand()%256,rand()%256)
#define NOIR        al_map_rgb(0,0,0)
#define ROUGE al_map_rgb(128+rand()%128,0,0)
#define MAXFOURMI 200

// les constantes
const int SCREENX=800;
const int SCREENY=600;

// les types
typedef struct {

    int x,y,dx,dy;            // déplacement
    int tx,ty;                // taille
    ALLEGRO_COLOR color;
    ALLEGRO_BITMAP*image;    

}fourmi;

// les fonctions
fourmi init_color_fourmi (ALLEGRO_COLOR color);
fourmi    move_fourmi                (fourmi f);
fourmi    collision_fourmi        (fourmi f1, fourmi f2);
void        affiche_fourmi            (fourmi f);
void nettoie_fourmi            (fourmi f);
void        erreur                    (const char*txt);

/***********************************************************
***********************************************************/
int main()
{
ALLEGRO_DISPLAY*display;
ALLEGRO_EVENT_QUEUE*queue;
ALLEGRO_TIMER*timer;
fourmi f[MAXFOURMI];

int i,j,fin=0;

    srand(time(NULL));

    if(!al_init())
        erreur("al_init()");

    display=al_create_display(SCREENX,SCREENY);
    if(!display)
        erreur("al_create_display");

    // obligatoire pour avoir le clavier
    if(!al_install_keyboard())
        erreur("al_install_keyboard()");

    // pour dessiner
    if(!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");

    // la file des événements
    queue=al_create_event_queue();
    if(!queue)
        erreur("al_create_event_queue()");

    // le topage
    timer=al_create_timer(1.0/60);

    // sélectionner les événements à considérer
    al_register_event_source(queue, al_get_display_event_source(display));
    al_register_event_source(queue, al_get_keyboard_event_source());
    al_register_event_source(queue, al_get_timer_event_source(timer));

    // initialisation fourmi
 for (i=0;i<MAXFOURMI;i++)
     f[i] = init_color_fourmi(ROUGE);

    // retour à l'affichage écran
    al_set_target_backbuffer(display);

    al_start_timer(timer);
    while(!fin){
        
        ALLEGRO_EVENT event={0}; 
        al_wait_for_event(queue,&event);

        // analyse évènements :

        if(event.type == ALLEGRO_EVENT_KEY_DOWN){ // clavier
            switch(event.keyboard.keycode){
                
                case ALLEGRO_KEY_ESCAPE:    fin=1;    break;
            }
        }
        else if(event.type==ALLEGRO_EVENT_DISPLAY_CLOSE)// fenêtre
            fin=1;
        else if(event.type==ALLEGRO_EVENT_TIMER){// timer

 for(i=0;i<MAXFOURMI;i++){// mouvement
                f[i] = move_fourmi(f[i]);
                for (j=0; j<MAXFOURMI; j++){
                    if(i==j)
                        continue;
                    f[i] = collision_fourmi(f[i], f[j]);
                }
            }
        }

        // affichage
        al_clear_to_color(NOIR); // efface écran

 for(i=0;i<MAXFOURMI;i++)
         affiche_fourmi(f[i]);

        al_flip_display();        // voir 

    }

 for(i=0;i<MAXFOURMI;i++)
        nettoie_fourmi(f[i]);

    al_destroy_display(display);
    al_destroy_event_queue(queue);
    return 0;
}
/***********************************************************
***********************************************************/

// (...)

/***********************************************************
collisions
***********************************************************/
fourmi collision_fourmi(fourmi f1, fourmi f2)
{
    if (f1.x < f2.x+f2.tx && f1.x+f1.tx > f2.x &&
            f1.y < f2.y+f2.ty && f1.y+f1.ty > f2.y ){
            f1.dx*=-1;
            f1.dy*=-1;
    }
    return f1;
}
/***********************************************************
***********************************************************/

VIII-D-4. Mise à jour du code, version référence

Dans la version pointeur, la fonction de collision n'a pas besoin de retourner la première fourmi qui est modifiée. Il y a un avantage certain à utiliser des pointeurs que nous allons exploiter dans la version suivante avec des combats : dans la fonction les deux fourmis peuvent être modifiées.

Colonie et collisions (version référence)
Sélectionnez
#include <allegro5\allegro.h>
#include <allegro5\allegro_native_dialog.h>
#include <allegro5\allegro_primitives.h>

// les define
#define COLORALEA    al_map_rgb(rand()%256,rand()%256,rand()%256)
#define NOIR        al_map_rgb(0,0,0)
#define ROUGE al_map_rgb(128+rand()%128,0,0)
#define MAXFOURMI 200

// les constantes
const int SCREENX = 800;
const int SCREENY = 600;

// les types
typedef struct {

    int x, y, dx, dy;            // déplacement
    int tx, ty;                // taille
    ALLEGRO_COLOR color;
    ALLEGRO_BITMAP*image;

}fourmi;

// les fonctions
fourmi* init_color_fourmi(ALLEGRO_COLOR color);
void        move_fourmi(fourmi* f);
void        affiche_fourmi(fourmi*f);
void        collision_fourmi(fourmi*f1, fourmi*f2);
void destroy_fourmi(fourmi**f);
void        erreur(const char*txt);

/***********************************************************
***********************************************************/
int main()
{
    ALLEGRO_DISPLAY*display;
    ALLEGRO_EVENT_QUEUE*queue;
    ALLEGRO_TIMER*timer;
    fourmi* f[MAXFOURMI];

    int i,j, fin = 0;

    srand(time(NULL));

    if (!al_init())
        erreur("al_init()");

    display = al_create_display(SCREENX, SCREENY);
    if (!display)
        erreur("al_create_display");

    // obligatoire pour avoir le clavier
    if (!al_install_keyboard())
        erreur("al_install_keyboard()");

    // pour dessiner
    if (!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");

    // la file des événements
    queue = al_create_event_queue();
    if (!queue)
        erreur("al_create_event_queue()");

    // le topage
    timer = al_create_timer(1.0 / 60);

    // sélectionner les événements à considérer
    al_register_event_source(queue, al_get_display_event_source(display));
    al_register_event_source(queue, al_get_keyboard_event_source());
    al_register_event_source(queue, al_get_timer_event_source(timer));

    // initialisation fourmi
    for (i = 0; i<MAXFOURMI; i++)
        f[i] = init_color_fourmi(ROUGE);

    // retour à l'affichage écran
    al_set_target_backbuffer(display);

    al_start_timer(timer);
    while (!fin){

        ALLEGRO_EVENT event = { 0 };
        al_wait_for_event(queue, &event);

        // analyse évènements :

        if (event.type == ALLEGRO_EVENT_KEY_DOWN){ // clavier
            switch (event.keyboard.keycode){

            case ALLEGRO_KEY_ESCAPE:    fin = 1;    break;
            }
        }
        else if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)// fenêtre
            fin = 1;
        else if (event.type == ALLEGRO_EVENT_TIMER){// timer

            for (i = 0; i < MAXFOURMI; i++){ // mouvement
                move_fourmi(f[i]);
                for (j = 0; j < MAXFOURMI; j++){
                    if (i == j)
                        continue;
                    collision_fourmi(f[i], f[j]);
                }
            }
        }

        // affichage
        al_clear_to_color(NOIR); // efface écran

        for (i = 0; i<MAXFOURMI; i++)
            affiche_fourmi(f[i]);

        al_flip_display();        // voir 

    }

    for (i = 0; i<MAXFOURMI; i++)
        destroy_fourmi(f + i); //équivalent &f[i]

    al_destroy_display(display);
    al_destroy_event_queue(queue);
    return 0;
}
/***********************************************************
***********************************************************/

// (...)

/***********************************************************
collisions
***********************************************************/
void collision_fourmi(fourmi*f1, fourmi*f2)
{
    if (f1->x < f2->x + f2->tx && f1->x + f1->tx > f2->x &&
        f1->y < f2->y + f2->ty && f1->y + f1->ty > f2->y){
        f1->dx *= -1;
        f1->dy *= -1;
        //f2->dx *= -1;
        //f2->dy *= -1;
    }
}
/***********************************************************
***********************************************************/

VIII-E. Collisions et combats

Lorsqu'il y a combat entre deux fourmis, elles doivent pouvoir se modifier mutuellement et dans la même fonction, les deux fourmis doivent pouvoir être modifiées. De ce fait nous allons privilégier la version référence du programme et abandonner la version valeur.

VIII-E-1. Combat

Lorsque deux fourmis entrent en collision, elles combattent. Le combat est symbolique : chacune tire un nombre au hasard et celle qui a le plus grand gagne. Le gain consiste à accroître sa couleur. Celle qui perd rebrousse chemin et part dans la direction opposée de celle qu'elle avait avant. La fonction a besoin en paramètre de deux références de fourmis parce que toutes les deux vont être modifiées par le combat :

 
Sélectionnez
void combat (fourmi*f1, fourmi*f2)
{
    int v1=rand();
    int v2=rand();
    if(v1>v2){
        change_couleur(f1, 1);
        f2->dx*=-1;
        f2->dy*=-1;
    }
    if (v2>v1){
        change_couleur(f2, 1);
        f1->dx*=-1;
        f1->dy*=-1;
    }
}

Le changement de couleur est opéré dans une fonction à part. La valeur de modification de la couleur est passée au paramètre val :

 
Sélectionnez
void change_couleur(fourmi*f, int val)
{
    unsigned char r, g, b;
    al_unmap_rgb(f->color, &r, &g, &b);
    r+=val;
    g+=val;
    b+=val;
    f->color = al_map_rgb(r, g, b);
}

Plusieurs perspectives sont ainsi ouvertes, par exemple diminuer les valeurs de couleur ou incrémenter avec des pas plus grands. Nous n'avons pas approfondi ici l'interaction.

Les combats conduisent à une métamorphose de l'espèce tout entière, visible avec les changements de couleur. Cela dit, comme l'incrémentation pour chaque composante de couleur est toujours de 1 et qu'il y a au total 255 graduations de couleur, chaque fourmi retrouve sa couleur de départ après 255 combats gagnés.

VIII-E-2. Modification de l'affichage

Le fait de modifier la couleur des fourmis oblige à revenir sur l'image de chaque fourmi afin de l'actualiser. Ainsi, dans la fonction d'affichage, il est nécessaire d'accéder en écriture à l'image de la fourmi passée en paramètre mais également, afin de pouvoir afficher ensuite la fourmi à l'écran, il faut aussi passer l'écran à la fonction. Ce qui donne :

 
Sélectionnez
void affiche_fourmi(fourmi*f, ALLEGRO_DISPLAY*display)
{
    // actualiser la couleur de la fourmi
    al_set_target_bitmap(f->image);
    al_clear_to_color(f->color);
    al_set_target_backbuffer(display);
    al_draw_bitmap(f->image,f->x, f->y, 0);
}

VIII-E-3. Modification de la fonction de détection de collision

Un combat a lieu uniquement en cas de collision. Du coup la fonction de collision doit simplement indiquer s'il y a rencontre ou pas. Elle retourne juste le test de collision de la façon suivante :

 
Sélectionnez
int collision_fourmi(fourmi*f1, fourmi*f2)
{
    return (f1->x < f2->x+f2->tx && f1->x+f1->tx > f2->x &&
    f1->y < f2->y+f2->ty && f1->y+f1->ty > f2->y );
}

VIII-E-4. Mise en œuvre des combats

Pour chaque fourmi, nous devons regarder toutes les autres et s'il y a collision appeler la fonction de combat pour les deux fourmis concernées ce qui donne :

 
Sélectionnez
for(i=0;i<MAXFOURMI;i++){// mouvement
    move_fourmi(f[i]);
    for (j=0; j<MAXFOURMI; j++){
        if(i!=j && collision_fourmi(f[i], f[j]) )
            combat(f[i],f[j]);
    }
}

VIII-E-5. Mise à jour du code version référence uniquement

Attention, il s'agit de la version référence (avec pointeurs) du programme et il n'y a pas de version valeur pour les fourmis :

Collisions et combats (version référence uniquement)
Sélectionnez
#include <allegro5\allegro.h>
#include <allegro5\allegro_native_dialog.h>
#include <allegro5\allegro_primitives.h>

// les define
#define COLORALEA    al_map_rgb(rand()%256,rand()%256,rand()%256)
#define NOIR        al_map_rgb(0,0,0)
#define ROUGE al_map_rgb(128+rand()%128,0,0)
#define MAXFOURMI 200

// les constantes
const int SCREENX=800;
const int SCREENY=600;

// les types
typedef struct {

    int x,y,dx,dy;            // déplacement
    int tx,ty;                // taille
    ALLEGRO_COLOR color;
    ALLEGRO_BITMAP*image;    

}fourmi;

// les fonctions
fourmi* init_color_fourmi (ALLEGRO_COLOR color);
void        move_fourmi                (fourmi*f);
int        collision_fourmi        (fourmi*f1, fourmi*f2);
void        combat                    (fourmi*f1, fourmi*f2);
void        change_couleur            (fourmi*f, int val);
void        affiche_fourmi            (fourmi*f, ALLEGRO_DISPLAY*display);
void nettoie_fourmi (fourmi*f);
void        erreur                    (const char*txt);

/***********************************************************
***********************************************************/
int main()
{
ALLEGRO_DISPLAY*display;
ALLEGRO_EVENT_QUEUE*queue;
ALLEGRO_TIMER*timer;
fourmi* f[MAXFOURMI];

int i,j,fin=0;

    srand(time(NULL));

    if(!al_init())
        erreur("al_init()");

    display=al_create_display(SCREENX,SCREENY);
    if(!display)
        erreur("al_create_display");

    // obligatoire pour avoir le clavier
    if(!al_install_keyboard())
        erreur("al_install_keyboard()");

    // pour dessiner
    if(!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");

    // la file des événements
    queue=al_create_event_queue();
    if(!queue)
        erreur("al_create_event_queue()");

    // le topage
    timer=al_create_timer(1.0/60);

    // sélectionner les événements à considérer
    al_register_event_source(queue, al_get_display_event_source(display));
    al_register_event_source(queue, al_get_keyboard_event_source());
    al_register_event_source(queue, al_get_timer_event_source(timer));

    // initialisation fourmi
 for (i=0;i<MAXFOURMI;i++)
     f[i] = init_color_fourmi(ROUGE);

    // retour à l'affichage écran
    al_set_target_backbuffer(display);

    al_start_timer(timer);
    while(!fin){
        
        ALLEGRO_EVENT event={0}; 
        al_wait_for_event(queue,&event);

        // analyse évènements :

        if(event.type == ALLEGRO_EVENT_KEY_DOWN){ // clavier
            switch(event.keyboard.keycode){
                
                case ALLEGRO_KEY_ESCAPE:    fin=1;    break;
            }
        }
        else if(event.type==ALLEGRO_EVENT_DISPLAY_CLOSE)// fenêtre
            fin=1;
        else if(event.type==ALLEGRO_EVENT_TIMER){// timer

            for(i=0;i<MAXFOURMI;i++){// mouvements et combats
             move_fourmi(f[i]);
                for (j=0; j<MAXFOURMI; j++){
                    if(i!=j && collision_fourmi(f[i], f[j]) )
                        combat(f[i],f[j]);
                }
            }
        }

        // affichage
        al_clear_to_color(NOIR); // efface écran

 for(i=0;i<MAXFOURMI;i++)
         affiche_fourmi(f[i],display);

        al_flip_display();        // voir 

    }

 for(i=0;i<MAXFOURMI;i++)
        nettoie_fourmi(f[i]);

    al_destroy_display(display);
    al_destroy_event_queue(queue);
    return 0;
}
/***********************************************************
initialisation
***********************************************************/

// (...)

/***********************************************************
affichage
***********************************************************/
void affiche_fourmi(fourmi*f, ALLEGRO_DISPLAY*display)
{
    // actualiser la couleur de la fourmi
    al_set_target_bitmap(f->image);
    al_clear_to_color(f->color);
    al_set_target_backbuffer(display);
    al_draw_bitmap(f->image,f->x, f->y, 0);

}
/***********************************************************
collisions
***********************************************************/
int collision_fourmi(fourmi*f1, fourmi*f2)
{
    return (f1->x < f2->x+f2->tx && f1->x+f1->tx > f2->x &&
            f1->y < f2->y+f2->ty && f1->y+f1->ty > f2->y );
            
}
/***********************************************************
combat
***********************************************************/
void combat (fourmi*f1, fourmi*f2)
{
    int v1=rand();
    int v2=rand();
    if(v1>v2){
        change_couleur(f1, 1);
        f2->dx*=-1;
        f2->dy*=-1;
    }
    if (v2>v1){
        change_couleur(f2, 1);
        f1->dx*=-1;
        f1->dy*=-1;
    }
}
/***********************************************************
métamorphose par combat
***********************************************************/
void change_couleur(fourmi*f, int val)
{
    unsigned char r, g, b;
        
    al_unmap_rgb(f->color, &r, &g, &b);
    r+=val;
    g+=val;
    b+=val;
    f->color = al_map_rgb(r, g, b);
}
/***********************************************************
***********************************************************/

VIII-F. Plusieurs colonies

Pour conclure avec nos fourmis, voici la mise en scène de plusieurs colonies simultanées. Nous n'avons pas approfondi les possibles relations entre les colonies et il n'y a pas de combat, juste plusieurs colonies en simultané.

VIII-F-1. Structure de données

Pour avoir plusieurs colonies, il nous faut plusieurs tableaux de fourmis, un par colonie. Il serait évidemment possible de les avoir en parallèle. Ou mieux de définir une structure colonie contenant un tableau de fourmis et d'autres possibles valeurs associées à la colonie. Ensuite il faudrait modifier toutes les fonctions de façon à traiter à chaque fois une colonie.

Mais afin d'obtenir plusieurs colonies sans grosses modifications du programme précédent, nous avons opté pour une matrice de fourmis définie de la façon suivante :

 
Sélectionnez
#define MAXFOURMI 200
#define MAXCOLONIE 4
(...)
t_fourmi f[MAXCOLONIE][MAXFOURMI];

Ainsi nous pourrons utiliser nos fonctions, il suffira de les appliquer pour chaque fourmi.

VIII-F-2. Initialisation

Chaque colonie est différenciée par la couleur de ses fourmis. La fonction d'initialisation d'une fourmi prend déjà en paramètre une couleur. Pour chaque colonie, une couleur est créée et toutes les fourmis de la colonie sont initialisées avec cette couleur de la façon suivante :

 
Sélectionnez
for (c=0; c<MAXCOLONIE; c++){
    ALLEGRO_COLOR color = COLORALEA; // une couleur par colonie
    for (i=0;i<MAXFOURMI;i++)
        f[c][i]= init_color_fourmi(color);
}

VIII-F-3. Affichage

Il suffit de parcourir l'ensemble des colonies et d'afficher chaque fourmi selon sa couleur. Il s'agit de la première fonction d'affichage qui n'a pas besoin de mettre à jour la couleur de la fourmi :

 
Sélectionnez
for (c=0; c<MAXCOLONIE;c++)
    for(i=0;i<MAXFOURMI;i++)
        affiche_fourmi(f[c][i]);

VIII-F-4. Mouvement

De même pour le mouvement, il s'agit de parcourir toutes les colonies et de bouger chaque fourmi :

 
Sélectionnez
for (c=0; c<MAXCOLONIE;c++)
    for(i=0;i<MAXFOURMI;i++)// mouvement
        f[c][i] = move_fourmi(f[c][i]);

VIII-F-5. Mise à jour du code

Il n'y a aucune modification des fonctions de notre version sans combat.

Plusieurs colonies de fourmis
Sélectionnez
#include <allegro5\allegro.h>
#include <allegro5\allegro_native_dialog.h>
#include <allegro5\allegro_primitives.h>

// les define
#define COLORALEA    al_map_rgb(rand()%256,rand()%256,rand()%256)
#define NOIR        al_map_rgb(0,0,0)
#define ROUGE al_map_rgb(128+rand()%128,0,0)

#define MAXFOURMI 200
#define MAXCOLONIE 4

// les constantes
const int SCREENX=800;
const int SCREENY=600;

// les types
typedef struct {

    int x,y,dx,dy;            // déplacement
    int tx,ty;                // taille
    ALLEGRO_COLOR color;
    ALLEGRO_BITMAP*image;    

}fourmi;

// les fonctions
fourmi init_color_fourmi (ALLEGRO_COLOR color);
fourmi    move_fourmi                (fourmi f);
void        affiche_fourmi            (fourmi f);
void nettoie_fourmi            (fourmi f);
void        erreur                    (const char*txt);

/***********************************************************
***********************************************************/
int main()
{
ALLEGRO_DISPLAY*display;
ALLEGRO_EVENT_QUEUE*queue;
ALLEGRO_TIMER*timer;
fourmi f[MAXCOLONIE][MAXFOURMI];

int c,i,fin=0;

    srand(time(NULL));

    if(!al_init())
        erreur("al_init()");

    display=al_create_display(SCREENX,SCREENY);
    if(!display)
        erreur("al_create_display");

    // obligatoire pour avoir le clavier
    if(!al_install_keyboard())
        erreur("al_install_keyboard()");

    // pour dessiner
    if(!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");

    // la file des événements
    queue=al_create_event_queue();
    if(!queue)
        erreur("al_create_event_queue()");

    // le topage
    timer=al_create_timer(1.0/60);

    // sélectionner les événements à considérer
    al_register_event_source(queue, al_get_display_event_source(display));
    al_register_event_source(queue, al_get_keyboard_event_source());
    al_register_event_source(queue, al_get_timer_event_source(timer));

    // initialisation fourmi
    for (c=0; c<MAXCOLONIE; c++){
        // une couleur par colonie
 ALLEGRO_COLOR color = COLORALEA; 
 for (i=0;i<MAXFOURMI;i++)
     f[c][i]= init_color_fourmi(color);
    }

    // retour à l'affichage écran
    al_set_target_backbuffer(display);

    al_start_timer(timer);
    while(!fin){
        
        ALLEGRO_EVENT event={0}; 
        al_wait_for_event(queue,&event);

        // analyse évènements :

        if(event.type == ALLEGRO_EVENT_KEY_DOWN){ // clavier
            switch(event.keyboard.keycode){
                
                case ALLEGRO_KEY_ESCAPE:    fin=1;    break;
            }
        }
        else if(event.type==ALLEGRO_EVENT_DISPLAY_CLOSE)// fenêtre
            fin=1;
        else if(event.type==ALLEGRO_EVENT_TIMER){// timer

 for (c=0; c<MAXCOLONIE;c++)
 for(i=0;i<MAXFOURMI;i++)// mouvement
             f[c][i] = move_fourmi(f[c][i]);

        }

        // affichage
        al_clear_to_color(NOIR); // efface écran

 for (c=0; c<MAXCOLONIE;c++)
 for(i=0;i<MAXFOURMI;i++)
         affiche_fourmi(f[c][i]);

        al_flip_display();        // voir 

    }
 for (c=0; c<MAXCOLONIE;c++)
 for(i=0; i<MAXFOURMI;i++)
 nettoie_fourmi(f[c][i]);

    al_destroy_display(display);
    al_destroy_event_queue(queue);
    return 0;
}
/***********************************************************
initialisation
***********************************************************/
fourmi init_color_fourmi(ALLEGRO_COLOR color)
{
fourmi f;

    f.tx=10;
    f.ty=10;
    f.x=rand()%(SCREENX-f.tx);
    f.y=rand()%(SCREENY-f.ty);
    f.dx=rand()%11-5;
    f.dy=rand()%11-5;
    f.color=color;
    f.image=al_create_bitmap(f.tx, f.ty);
    if(!f.image)
        erreur("f.image=al_create_bitmap(f.tx, f.ty)");
    
    // obligatoire pour pouvoir dessiner dans une bitmap mémoire
    al_set_target_bitmap(f.image);
    al_clear_to_color(f.color);
    return f;
}
/***********************************************************
***********************************************************/

// (...)

/***********************************************************
***********************************************************/

VIII-F-6. Mise à jour du code, version référence

Pour la version référence ce sont les mêmes modifications :

Plusieurs colonies de fourmis (version référence)
Sélectionnez
#include <allegro5\allegro.h>
#include <allegro5\allegro_native_dialog.h>
#include <allegro5\allegro_primitives.h>

#define COLORALEA    al_map_rgb(rand()%256,rand()%256,rand()%256)
#define NOIR        al_map_rgb(0,0,0)
#define ROUGE al_map_rgb(128+rand()%128,0,0)

#define MAXFOURMI        200
#define MAXCOLONIE    4

const int SCREENX = 800;
const int SCREENY = 600;

typedef struct {

    int x, y, dx, dy;            
    int tx, ty;                
    ALLEGRO_COLOR color;
    ALLEGRO_BITMAP*image;

}fourmi;


fourmi* init_color_fourmi(ALLEGRO_COLOR color);
void    move_fourmi(fourmi* f);
void        affiche_fourmi(fourmi*f);
void destroy_fourmi(fourmi**f);
void        erreur(const char*txt);

/***********************************************************
***********************************************************/
int main()
{
    ALLEGRO_DISPLAY*display;
    ALLEGRO_EVENT_QUEUE*queue;
    ALLEGRO_TIMER*timer;
    // plusieurs colonies
    fourmi* f[MAXCOLONIE][MAXFOURMI];

    int c,i, fin = 0;

    srand(time(NULL));

    if (!al_init())
        erreur("al_init()");

    display = al_create_display(SCREENX, SCREENY);
    if (!display)
        erreur("al_create_display");

    if (!al_install_keyboard())
        erreur("al_install_keyboard()");

    if (!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");

    queue = al_create_event_queue();
    if (!queue)
        erreur("al_create_event_queue()");

    timer = al_create_timer(1.0 / 60);

    al_register_event_source(queue, al_get_display_event_source(display));
    al_register_event_source(queue, al_get_keyboard_event_source());
    al_register_event_source(queue, al_get_timer_event_source(timer));

    // initialisation fourmi
    for (c = 0; c < MAXCOLONIE; c++){
        ALLEGRO_COLOR color = COLORALEA;
        for (i = 0; i < MAXFOURMI; i++)
            f[c][i] = init_color_fourmi(color);
    }

    // retour à l'affichage écran
    al_set_target_backbuffer(display);

    al_start_timer(timer);
    while (!fin){

        ALLEGRO_EVENT event = { 0 };
        al_wait_for_event(queue, &event);

        if (event.type == ALLEGRO_EVENT_KEY_DOWN){
            switch (event.keyboard.keycode){

            case ALLEGRO_KEY_ESCAPE:    fin = 1;    break;
            }
        }
        else if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
            fin = 1;
        else if (event.type == ALLEGRO_EVENT_TIMER){
            
            for (c = 0; c < MAXCOLONIE; c++)
                for (i = 0; i<MAXFOURMI; i++)
                    move_fourmi(f[c][i]);

        }

        al_clear_to_color(NOIR);

        for (c = 0; c < MAXCOLONIE; c++)
            for (i = 0; i<MAXFOURMI; i++)
                affiche_fourmi(f[c][i]);

        al_flip_display();        

    }

    for (c = 0; c < MAXCOLONIE; c++)
        for (i = 0; i<MAXFOURMI; i++)
            destroy_fourmi(&f[c][i]); 

    al_destroy_display(display);
    al_destroy_event_queue(queue);
    return 0;
}
/***********************************************************
initialisation
***********************************************************/
fourmi* init_color_fourmi(ALLEGRO_COLOR color)
{
    fourmi *f = (fourmi*)malloc(sizeof(fourmi));

    f->tx = 10;
    f->ty = 10;
    f->x = rand() % (SCREENX - f->tx);
    f->y = rand() % (SCREENY - f->ty);
    f->dx = rand() % 11 - 5;
    f->dy = rand() % 11 - 5;
    f->color = color;
    f->image = al_create_bitmap(f->tx, f->ty);
    if (!f->image)
        erreur("f.image=al_create_bitmap(f.tx, f.ty)");

    // obligatoire pour pouvoir dessiner dans une bitmap mémoire
    al_set_target_bitmap(f->image);
    al_clear_to_color(f->color);
    return f;
}
/***********************************************************
***********************************************************/

// (...)

/***********************************************************
***********************************************************/

VIII-G. Mouvements d'ensemble des fourmis

VIII-G-1. Principe

Dans une colonie, les fourmis se déplacent mais elles peuvent être sensibles à l'attraction des autres fourmis alentour en fonction de la distance ou de la proximité.

La sensibilité à l'attraction collective des fourmis est dosée avec le mouvement horizontal de la souris. Totalement à gauche le collectif supprime toute possibilité de mouvement individuel. Totalement à droite les fourmis n'ont aucune interaction. Le mouvement vertical de la souris contrôle la distance des interactions possibles. Dans les deux cas, toutes les graduations intermédiaires sont permises.

Il s'agit à l'origine d'un algorithme créé par un ami, Robin Fercoq, et intitulé « Volpurna ». L'auteur en donnait la description suivante : « Volpurna simule l'agitation de particules en interaction. Chaque particule peut choisir soit de suivre son propre déplacement rectiligne, soit d'adopter le mouvement de la particule la plus proche au-delà d'une certaine distance. La probabilité de suivre plutôt son « instinct » ou plutôt le mouvement des autres est réglée par l'utilisateur, de même que la distance minimum d'interaction. En manipulant ces paramètres globaux et leur variabilité interindividuelle, il est possible d'explorer continuellement différents régimes collectifs, moléculaires, chaotiques, individualistes ou totalitaires ».

VIII-G-2. Structure de données

Une fourmi a toujours une position qui sont deux variables entières x et y.

Son déplacement est partagé en trois options. Ce sont :

  • Son déplacement lorsqu'elle est maîtresse d'elle-même et non sujette à influence. C'est le déplacement « maître » composé de deux variables entières mdx et mdy.
  • Son déplacement courant qui correspond à la direction d'une entité soumise ou non à influence. Ce sont deux variables entières cdx et cdy.
  • Deux variables temporaires tmpdx et tmpdy stockent des valeurs intermédiaires de directions pendant le calcul des influences mutuelles entre entités.

La couleur des fourmis est soumise au même principe d'échange que le déplacement. En fonction du contexte, une fourmi va changer de direction mais aussi de couleur. Ainsi la fourmi possède trois couleurs : sa couleur maître mcol, sa couleur courante ccol et une couleur temporaire tmpcol.

Une fourmi complète est ainsi constituée par une structure du type :

 
Sélectionnez
typedef struct{
    int x, y; // position
    int mdx, mdy; // déplacement maître
    int cdx, cdy; // déplacement courant
    int tmpdx, tmpdy; // pour calculer le déplacement
    ALLEGRO_COLOR mcol, ccol, tmpcol;
    // idem pour la couleur
}fourmi;

Pour avoir une colonie, il faut un ensemble de fourmis, c'est-à-dire un tableau de fourmis dont la taille est spécifiée par une macro constante :

 
Sélectionnez
#define MAXFOURMI 300
fourmi f[MAXFOURMI];

VIII-G-3. Initialisation des fourmis

Dans ce programme nous considérons l'ensemble des fourmis pour chaque action et les fonctions prennent en paramètre le tableau des fourmis. Au départ les valeurs sont aléatoires dans une fourchette.

La position se trouve dans l'écran dont la taille est définie avec deux constantes :

 
Sélectionnez
const int SCREENX = 800;
const int SCREENY = 600;

L'obtention d'une valeur aléatoire dans une fourchette est définie par une directive en vue d'alléger l'écriture de la façon suivante :

 
Sélectionnez
#define irand(n) (rand()%(n))

Plutôt que de prendre des couleurs aléatoires ce qui est très pauvre au point de vue informationnel, nous préférons cette fois créer une palette de couleurs. En effet le fait de contraindre les couleurs en soumettant leurs compositions à un ordre produit un commencement d'harmonie des couleurs et c'est plus intéressant visuellement.

VIII-G-3-a. Fonction d'initialisation des fourmis

La fonction d'initialisation prend en paramètre un tableau de fourmis. Dans un premier temps, elle crée une palette de 256 couleurs (la fonction de création de couleur est détaillée ci­dessous). La palette est tout simplement un tableau de structure ALLEGRO_COLOR .

Ensuite des valeurs sont attribuées pour tous les champs de chaque fourmi en respectant les fourchettes nécessaires. La fourmi doit rester dans l'écran. Le déplacement est une valeur comprise entre -5 et 5. Au départ, déplacement maître et déplacement courant sont égaux. La couleur est puisée au hasard dans la palette créée au départ et de même que pour le déplacement couleur maîtresse et couleur courante sont égales. La fonction complète est la suivante :

 
Sélectionnez
void init_fourmis(fourmi f[])
{
    int i;
    ALLEGRO_COLOR color[256];
    avoir une palette de couleurs
            init_couleurs(color);
    // initialisation des fourmis
    for (i = 0; i<MAXFOURMI; i++){
        f[i].x = irand(SCREENX);
        f[i].y = irand(SCREENY);
        f[i].mdx = f[i].cdx = irand(11) - 5;
        f[i].mdy = f[i].cdy = irand(11) - 5;
        f[i].mcol = f[i].ccol = color[irand(256)];
    }
}

VIII-G-3-b. Création d'une palette de couleurs

Une palette est un ensemble de couleurs, ici c'est un ensemble de 256 couleurs. C'est-à-dire un tableau de ALLEGRO_COLOR :

 
Sélectionnez
ALLEGRO_COLOR p[256]

L'idée est d'avoir des couleurs en dégradé pour chaque palette et la possibilité de créer des palettes toujours différentes.

Une couleur est constituée de trois valeurs entières comprises entre 0 et 255, une pour le rouge, une pour le vert et une pour le bleu. Dans notre palette il y a 256 couleurs. Nous allons créer une progression régulière d'une couleur à l'autre en augmentant ou en diminuant la valeur de chaque composante. Lorsque la valeur d'une composante atteint une borne 0 ou 255 alors on inverse sa progression.

Nous avons choisi une progression de 4 (augmentation) ou de -4 (diminution) pour chaque composante. Éventuellement une composante peut ne pas être soumise à progression avec une valeur de 0.

Pour chaque couleur le calcul des trois composantes est stocké dans un tableau de trois entiers :

 
Sélectionnez
int c[3];

L'indice 0 donne la valeur de rouge, l'indice 1 la valeur de vert et l'indice 2 la valeur de bleu.

Pour chaque couleur la valeur de progression de chaque couleur est stockée dans un tableau de trois entiers :

 
Sélectionnez
int v[3];

L'indice 0 donne la progression de la composante de rouge, l'indice 1 celle du vert et l'indice 2 celle du bleu. Nous avons dit que la progression est toujours de 4 ainsi dans ce tableau nous stockons uniquement la direction : 1 pour une augmentation, -1 pour une diminution et 0 pour aucune modification. Le résultat pour chaque couleur est obtenu de la façon suivante :

 
Sélectionnez
for (j = 0; j<3; j++){
    c[j] += v[j] * 4;
    // +0, +4 ou -4

Pour que l'algorithme soit complet, il manque une initialisation de départ et le contrôle des bords pour chaque composante.

Au départ, chaque composante de la première couleur dans la palette prend une valeur aléatoire dans la fourchette de 0 à 255 compris. Pour chaque composante également, la progression est initialisée avec une valeur choisie aléatoirement entre -1, 0 et 1. Nous avons ainsi la première séquence de code suivante :

 
Sélectionnez
for (i = 0; i < 3; i++){
    c[i] = irand(256) ; // départ entre 0 et 255
    v[i] = irand(3) - 1; // -1, 0, 1
}

Ensuite chaque couleur est constituée dans une seconde boucle de la façon suivante :

 
Sélectionnez
for (i = 0; i<256; i++){
    r = c[0];
    g = c[1];
    b = c[2];
    p[i] = al_map_rgb(r, g, b);
    (...)

et toujours dans la même boucle est effectuée la progression de chaque composante :

 
Sélectionnez
// faire progresser la couleur.
for (j = 0; j<3; j++){
    c[j] += v[j] * 4;
    // +0, +4 ou -4
    (...)

Dans cette boucle il faut également contrôler si pour une composante, un bord est touché et si oui inverser la progression, ce qui donne :

 
Sélectionnez
for (j = 0; j<3; j++){
    (...)
    // si une borne est rencontrée inverser le mouvement
    if (c[j]<0){
        c[j] = 0;
        v[j] = 1;
    }
    else if (c[j]>255){
        c[j] = 255;
        v[j] = -1;
    }

Il reste juste une sorte de coquetterie : l'idée d'introduire des perturbations aléatoires afin de rompre une éventuelle monotonie du processus de progression. À cet effet de temps en temps, de façon aléatoire la progression d'une des composantes est modifiée sans pour autant avoir atteint l'un des bords. C'est cette dernière séquence de l'algorithme :

 
Sélectionnez
for (j = 0; j<3; j++){
    (...)
    // introduire un peu de désordre
    else if (!irand(32))
        v[j] = irand(3) - 1;
}

Voici la version complète :

 
Sélectionnez
void init_couleurs(ALLEGRO_COLOR p[256])
{
    int c[3], v[3];
    int i, j;
    unsigned char r, g, b;
    for (i = 0; i < 3; i++){
        c[i] = irand(256) ; // départ entre 0 et 255
        v[i] = irand(3) - 1; // -1, 0, 1
    }
    for (i = 0; i<256; i++){
        r = c[0];
        g = c[1];
        b = c[2];
        p[i] = al_map_rgb(r, g, b);
        // faire progresser la couleur.
        for (j = 0; j<3; j++){
            c[j] += v[j] * 4;
            // +0, +4 ou -4
            // si une borne est rencontrée inverser le mouvement
            if (c[j]<0){
                c[j] = 0;
                v[j] = 1;
            }
            else if (c[j]>255){
                c[j] = 255;
                v[j] = -1;
            }
            // introduire un peu de désordre
            else if (!irand(32))
                v[j] = irand(3) - 1;
        }
    }
}

L'étendue des possibilités de création de palette de cette fonction peut être augmentée en jouant sur la progression : elle pourrait ne pas être systématiquement de 4 et chaque composante pourrait avoir une progression différente.

VIII-G-4. Affichage

L'affichage regroupe l'affichage des fourmis et par­dessus l'affichage de la mire représentée par une ligne horizontale et une ligne verticale qui traversent l'écran en se croisant aux coordonnées de la souris.

L'affichage est très simple : l'écran est tout d'abord entièrement effacé et ensuite un rectangle plein de 10 pixels est dessiné à la position de chaque fourmi.

La fonction prend en paramètre l'ensemble des fourmis et aussi les coordonnées de la souris afin de pouvoir tracer la mire.

La couleur noire est définie préalablement avec une directive pour l'effacement de l'écran :

 
Sélectionnez
#define NOIR al_map_rgb(0,0,0)

Ainsi que la couleur verte pour la mire :

 
Sélectionnez
#define VERT al_map_rgb(0,255,0)

Le tracé de la ligne verticale passe par la coordonnée mx de la souris et est obtenu de la façon suivante :

 
Sélectionnez
al_draw_line(mx, 0, mx, SCREENY, VERT, 2);

Le tracé de la ligne horizontale passe par la coordonnée my de la souris, il est obtenu avec :

 
Sélectionnez
al_draw_line(0, my, SCREENX, my, VERT, 2);

La fonction complète est la suivante :

 
Sélectionnez
void affiche_fourmis(fourmi f[], int mx, int my)
{
    int i;
    // effacer
    al_clear_to_color(NOIR);
    // afficher chaque fourmi
    for (i = 0; i<MAXFOURMI; i++){
        al_draw_filled_rectangle(f[i].x, f[i].y,
                                 f[i].x + 10, f[i].y + 10,
                                 f[i].ccol);
    }
    // lignes verticale et horizontale
    al_draw_line(mx, 0, mx, SCREENY, VERT, 2); // verticale en mx
    al_draw_line(0, my, SCREENX, my, VERT, 2); // horizontale en my
}

VIII-G-5. Mouvement

Le mouvement de chaque fourmi peut être influencé par celui de ses voisines. Le contrôle de cette influence répond à deux modalités :

  • La force de l'influence dépend de la position horizontale de la souris. Si la souris est totalement à gauche, c'est le groupe qui domine et l'individu n'existe plus. Si la souris est totalement à droite, c'est l'individu qui domine et il n'y a plus aucune interaction avec les autres.
  • La portée de l'influence est déterminée par la position verticale de la souris. Si la souris est totalement en haut, les interactions ont lieu dans la plus grande proximité. Plus la souris descend, plus les interactions se font entre fourmis plus éloignées.

Précisons également que les fourmis sont dans un écran circulaire. En quelque sorte il n'y a pas de bord ; sortir d'un côté revient à entrer de l'autre.

L'algorithme se décompose en deux grandes étapes. La première consiste à trouver quel est le déplacement de chaque fourmi en fonction du réglage opéré par l'utilisateur qui détermine le taux d'individualité et la distance des interactions. La seconde étape consiste simplement pour chaque fourmi à appliquer les résultats obtenus.

VIII-G-5-a. Détermination du mouvement

Le premier point détermine pour chaque fourmi, en fonction de la position horizontale de la souris, si elle avance à partir de sa direction maîtresse mdx et mdy. Si oui le calcul pour cette fourmi est terminé et cette direction est sauvegardée en tmpdx et tmpdy.

Sinon cela signifie que la fourmi est soumise à l'influence de la fourmi la plus proche d'elle. Mais cette proximité est contrainte par la position verticale de la souris. Si la souris est tout en haut de l'écran, la plus proche sera effectivement la plus proche. En revanche plus la souris descend et plus il y a une distance minimum à respecter qui augmente. La fourmi la plus proche est alors la fourmi la plus proche au-delà d'une certaine distance.

La fonction prend en paramètre l'ensemble des fourmis ainsi que la position de la souris.

Les fourmis sont prises une par une dans une boucle for. Le premier point détermine si la fourmi est soumise ou non à influence. Cette influence dépend de la position horizontale de la souris. Elle n'a pas besoin d'être absolue, elle peut être statistique. On tire un nombre au hasard compris entre 0 et SCREENX, la taille de l'écran. Si cette valeur est inférieure à la position horizontale de la souris alors la fourmi n'est pas soumise à influence et prend ses valeurs maîtresses de déplacement mdx et mdy pour se déplacer :

 
Sélectionnez
for (i = 0; i<MAXFOURMI; i++){
    // 1 INDIVIDUALITÉ :
    // probabilité en relation avec la position mx horizontale
    // de la souris
    if (irand(SCREENX) < mx){
        f[i].tmpdx = f[i].mdx;
        f[i].tmpdy = f[i].mdy;
        f[i].tmpcol = f[i].mcol;
        continue;
    }
    (...)

Ainsi plus la souris est à droite, plus il y a de chance pour que la fourmi ne soit pas soumise à influence et au contraire plus la souris est à gauche, plus elle est soumise à influence.

Si elle n'est pas soumise à influence, on passe à la fourmi suivante avec l'instruction continue. Sinon il faut chercher quelle autre fourmi influence la fourmi courante. Il faut trouver la fourmi la plus proche.

Pour ce faire, nous devons calculer la distance entre la fourmi courante et toutes les autres et repérer la plus courte distance. Le calcul de la distance se fait avec le théorème de Pythagore : distance² = dx² + dy²

dx et dy sont obtenus en soustrayant les positions en x et y de la fourmi courante avec successivement les positions x et y de chacune des autres fourmis, le résultat étant pris en valeur absolue :

 
Sélectionnez
for (i = 0; i<MAXFOURMI; i++){
    (...)
    // trouver la fourmi la plus proche
    id_fourmi = -1;
    mindist = SCREENX*SCREENX; // distance la plus grande
    for (n = 0; n<MAXFOURMI; n++){ // regarder chaque autre fourmi
        if (n == i) // passer la fourmi courante i
            continue;
        // pour calcul de la distance
        dx = abs(f[i].x - f[n].x);
        dy = abs(f[i].y - f[n].y);
        (...)

Là intervient un réglage lié à l'écran circulaire. Si deux fourmis sont espacées de plus de la moitié de l'écran, par exemple à l'horizontale alors la mesure qui nous intéresse est celle qui passe par l'autre côté, comme si l'on était sur un cylindre :

Image non disponible

Pour l'obtenir, si la distance obtenue est supérieure à la moitié de l'écran il suffit de prendre la différence entre cette distance et la taille de l'écran, et ceci pour l'horizontale et pour la verticale ce qui donne :

 
Sélectionnez
(...)
//
dx = abs(f[i].x - f[n].x);
dy = abs(f[i].y - f[n].y);
// si dx > à la moitié de l'écran passer par les "bords"
if (dx > SCREENX / 2)
dx = SCREENX - dx;
if (dy > SCREENY / 2)
dy = SCREENY - dy;

Une fois obtenus dx et dy, nous pouvons calculer la valeur de l'hypoténuse qui donne la distance entre les deux fourmis :

 
Sélectionnez
(...)
// calcul de la distance
dist = dx*dx + dy*dy;

Si cette distance dist est inférieure à mindist alors nous la conservons. Mais attention, le test n'est pas complet. Nous cherchons la plus courte distance au-delà de la distance indiquée par la position verticale de la souris. Il faut transformer un peu cette valeur de la position verticale de la souris et nous prenons :

mx² / 2

Voici maintenant le test complet avec la récupération de l'indice de la fourmi candidate trouvée :

 
Sélectionnez
if (dist < mindist && dist >(my*my) / 2){
    id_fourmi = n;
    mindist = dist;
}

Si plus tard une autre fourmi est trouvée plus proche tout en respectant la contrainte exercée par la position de la souris, c'est elle qui sera conservée et ainsi de suite jusqu'à trouver la bonne.

Une fois la fourmi qui est source de l'influence trouvée, il reste à récupérer cette influence, c'est-à-dire à recopier ses valeurs de déplacement courant dans les champs tmpdx et tmpdy, ainsi que sa couleur courante en tmpcol :

 
Sélectionnez
// si une fourmi trouvée, prendre sa direction
// et sa couleur courantes
if (id_fourmi >= 0){
    f[i].tmpdx = f[id_fourmi].cdx;
    f[i].tmpdy = f[id_fourmi].cdy;
    f[i].tmpcol = f[id_fourmi].ccol;
}

Voici le code complet de la fonction :

 
Sélectionnez
void calcul_fourmis(fourmi f[], int mx, int my)
{
    int i, n, id_fourmi;
    int mindist, dist, dx, dy;
    for (i = 0; i<MAXFOURMI; i++){
        // 1 INDIVIDUALITÉ :
        // probabilité en relation avec la position mx horizontale
        // de la souris
        if (irand(SCREENX) < mx){
            f[i].tmpdx = f[i].mdx;
            f[i].tmpdy = f[i].mdy;
            f[i].tmpcol = f[i].mcol;
            continue;
        }
        // PORTÉE DES INTERACTIONS
        // trouver la fourmi la plus proche (pour prendre
        // ses valeurs ensuite)
        id_fourmi = -1;
        mindist = SCREENX*SCREENX;
        for (n = 0; n<MAXFOURMI; n++){
            if (n == i)
                continue;
            // théorème de Pythagore, écran circulaire
            dx = abs(f[i].x - f[n].x);
            // si dx > à la moitié de l'écran passer par les "bords"
            if (dx > SCREENX / 2)
                dx = SCREENX - dx;
            dy = abs(f[i].y - f[n].y);
            if (dy > SCREENY / 2)
                dy = SCREENY - dy;
            // calcul de la distance
            dist = dx*dx + dy*dy;
            // la plus proche avec contrainte de la position
            // verticale de la souris
            if (dist < mindist && dist >(my*my) / 2){
                id_fourmi = n;
                mindist = dist;
            }
        }
        // si une fourmi trouvée, prendre sa direction
        // et sa couleur courantes
        if (id_fourmi >= 0){
            f[i].tmpdx = f[id_fourmi].cdx;
            f[i].tmpdy = f[id_fourmi].cdy;
            f[i].tmpcol = f[id_fourmi].ccol;
        }
    }
}

VIII-G-5-b. Actualisation

Une fois le mouvement à effectuer par chaque fourmi déterminé grâce à la fonction calcul_fourmis() il reste à l'accomplir.

Dans une boucle for , prendre chaque fourmi et lui affecter comme valeurs courantes les valeurs temporaires stockées, ensuite effectuer le déplacement en ajoutant les valeurs de déplacements courants à la position. C'est la fonction :

 
Sélectionnez
void move_fourmis(fourmi f[])
{
    int i;
    for (i = 0; i<MAXFOURMI; i++){
        // mettre à jour déplacement et couleur courants
        f[i].cdx = f[i].tmpdx;
        f[i].cdy = f[i].tmpdy;
        f[i].ccol = f[i].tmpcol;
        // bouger dans écran circulaire
        f[i].x = (f[i].x + f[i].cdx + SCREENX) % SCREENX;
        f[i].y = (f[i].y + f[i].cdy + SCREENY) % SCREENY;
    }
}

VIII-G-6. Changement de couleur

Le programme offre la possibilité de réinitialiser les couleurs de l'ensemble des fourmis lorsque la touche [Entrée] est pressée. Pour ce faire, dans une fonction dédiée, une nouvelle palette est créée et la couleur maîtresse de chaque fourmi est remplacée par une nouvelle couleur prise aléatoirement dans la nouvelle palette. C'est la fonction suivante :

 
Sélectionnez
void change_couleur_fourmis(fourmi f[])
{
    int i;
    ALLEGRO_COLOR c[256];
    init_couleurs(c);
    for (i = 0; i<MAXFOURMI; i++)
        f[i].mcol = c[irand(256)];
}

VIII-G-7. Initialisation du programme

Afin de clarifier le main() nous avons mis en global la fenêtre display , la file d'événements et le timer ainsi que les constantes pour la taille de l'écran :

 
Sélectionnez
// globales
ALLEGRO_DISPLAY*DISPLAY;
ALLEGRO_EVENT_QUEUE*QUEUE;
ALLEGRO_TIMER*TIMER;
const int SCREENX = 800;
const int SCREENY = 600;

Ensuite toutes les initialisations liées à la mise en place d'Allegro sont effectuées dans une fonction de la façon suivante :

 
Sélectionnez
void init_prog()
{
    if (!al_init())
        erreur("al_init()");
    DISPLAY = al_create_display(SCREENX, SCREENY);
    if (!DISPLAY)
        erreur("al_create_display");
    if (!al_install_keyboard())
        erreur("al_install_keyboard()");
    if (!al_install_mouse())
        erreur("al_install_mouse()");
    if (!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");
    QUEUE = al_create_event_queue();
    if (!QUEUE)
        erreur("al_create_event_queue()");
    TIMER = al_create_timer(1.0 / 60);
    al_register_event_source(QUEUE, al_get_display_event_source(DISPLAY));
    al_register_event_source(QUEUE, al_get_keyboard_event_source());
    al_register_event_source(QUEUE, al_get_mouse_event_source());
    al_register_event_source(QUEUE, al_get_timer_event_source(TIMER));
    al_start_timer(TIMER);
}

De même pour les libérations de mémoire à la fin du programme, les appels des fonctions pour les éléments concernés sont regroupés dans une procédure :

 
Sélectionnez
void destructeur()
{
    al_destroy_display(DISPLAY);
    al_destroy_event_queue(QUEUE);
    al_destroy_timer(TIMER);
}

VIII-G-8. Code complet

Voici le code complet du programme :

Fourmis et mouvements collectifs
Sélectionnez
#include <allegro5\allegro.h>
#include <allegro5\allegro_native_dialog.h>
#include <allegro5\allegro_primitives.h>

#define MAXFOURMI    300
#define irand(n)    (rand()%(n))
#define VERT        al_map_rgb(0,255,0)
#define NOIR        al_map_rgb(0,0,0)

// globales
ALLEGRO_DISPLAY*DISPLAY;
ALLEGRO_EVENT_QUEUE*QUEUE;
ALLEGRO_TIMER*TIMER;

const int SCREENX = 800;
const int SCREENY = 600;

// une fourmi
typedef struct{

    int x, y;            // position
    int mdx, mdy;        // déplacement maitre
    int cdx, cdy;        // déplacement courant
    int tmpdx, tmpdy;    // pour calculer le déplacement 
    ALLEGRO_COLOR mcol, ccol, tmpcol;    // idem pour la couleur

}fourmi;


// les fonctions
void    init_prog(void);
void    init_fourmis(fourmi f[]);
void    init_couleurs(ALLEGRO_COLOR p[256]);
void    calcul_fourmis(fourmi f[], int mx, int my);
void    move_fourmis(fourmi f[]);
void    affiche_fourmis(fourmi f[], int mx, int my);
void    change_couleur_fourmis(fourmi f[]);
void    destructeur(void);
void    erreur(const char*txt);

/***********************************************************
***********************************************************/
int main()
{
    fourmi f[MAXFOURMI];
    int mouse_x, mouse_y;
    int fin = 0;

    srand(time(NULL));

    init_prog();
    init_fourmis(f);

    while (!fin){

        ALLEGRO_EVENT event;

        al_wait_for_event(QUEUE, &event);

        if (event.type == ALLEGRO_EVENT_KEY_DOWN){
            switch (event.keyboard.keycode){
            case ALLEGRO_KEY_ENTER:
                change_couleur_fourmis(f);
                break;

            case ALLEGRO_KEY_ESCAPE:
                fin = 1;
                break;
            }
        }
        else if (event.type == ALLEGRO_EVENT_MOUSE_AXES){
            mouse_x = event.mouse.x;
            mouse_y = event.mouse.y;
        }
        else if (event.type = ALLEGRO_EVENT_TIMER){

            calcul_fourmis(f, mouse_x, mouse_y);
            move_fourmis(f);
        }
        else if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
            fin = 1;

        // affichage
        affiche_fourmis(f, mouse_x, mouse_y);
        al_flip_display();

    }

    destructeur();
    return 0;
}
/***********************************************************
initialisation
***********************************************************/
void init_fourmis(fourmi f[])
{
    int i;
    ALLEGRO_COLOR color[256];

    // avoir une palette de couleur 
    init_couleurs(color);

    // initialistion des fourmis
    for (i = 0; i<MAXFOURMI; i++){
        f[i].x = irand(SCREENX);
        f[i].y = irand(SCREENY);
        f[i].mdx = f[i].cdx = irand(11) - 5;
        f[i].mdy = f[i].cdy = irand(11) - 5;
        f[i].mcol = f[i].ccol = color[irand(256)];
    }
}
/*
constuire une palette de 256 couleurs style dégradé
*/
void init_couleurs(ALLEGRO_COLOR p[256])
{
    int c[3], v[3];
    int i, j;
    unsigned char r, g, b;

    for (i = 0; i < 3; i++){
        c[i] = irand(256);            // départ entre 0 et 255
        v[i] = irand(3) - 1;            // -1, 0, 1
    }
    for (i = 0; i<256; i++){
        r = c[0];
        g = c[1];
        b = c[2];
        p[i] = al_map_rgb(r, g, b);

        for (j = 0; j<3; j++){
            c[j] += v[j] * 4;        // +0, +4 ou -4
            if (c[j]<0){
                c[j] = 0;
                v[j] = 1;
            }
            else if (c[j]>255){
                c[j] = 255;
                v[j] = -1;
            }
            // introduire un peu de désordre
            else if (!irand(32))
                v[j] = irand(3) - 1;
        }
    }
}
/***********************************************************
les fourmis en mouvement
***********************************************************/
void calcul_fourmis(fourmi f[], int mx, int my)
{
    int i, n, id_fourmi;
    int mindist, dist, dx, dy;

    for (i = 0; i<MAXFOURMI; i++){

        // 1 INDIVIDUALITE :
        // proba en relation avec la position mx horizontale
        // de la souris
        if (irand(SCREENX) < mx){
            f[i].tmpdx = f[i].mdx;
            f[i].tmpdy = f[i].mdy;
            f[i].tmpcol = f[i].mcol;
            continue;
        }

        // PORTEE DES INTERACTIONS

        // trouver la fourmi la plus proche (pour prendre 
        // ses valeurs ensuite)
        id_fourmi = -1;
        mindist = SCREENX*SCREENX;
        for (n = 0; n<MAXFOURMI; n++){
            if (n == i)
                continue;

            // théorème de pythagore, écran circulaire
            dx = abs(f[i].x - f[n].x);
            // si dx > à la moitiè de l'écran passer par les "bords"
            if (dx > SCREENX / 2)
                dx = SCREENX - dx;

            dy = abs(f[i].y - f[n].y);
            if (dy > SCREENY / 2)
                dy = SCREENY - dy;

            // calcul de la distance
            dist = dx*dx + dy*dy;
            // le plus proche et contrainte de la position verticale de la souris
            if (dist < mindist && dist >(my*my) / 2){
                id_fourmi = n;
                mindist = dist;
            }
        }
        // si une fourmi trouvé, prendre sa direction et couleur courants
        if (id_fourmi >= 0){
            f[i].tmpdx = f[id_fourmi].cdx;
            f[i].tmpdy = f[id_fourmi].cdy;
            f[i].tmpcol = f[id_fourmi].ccol;
        }
    }
}
/***********************************************************
mouvement
***********************************************************/
void move_fourmis(fourmi f[])
{
    int i;
    for (i = 0; i<MAXFOURMI; i++){

        // mettre à jour deplacement et couleur courants
        f[i].cdx = f[i].tmpdx;
        f[i].cdy = f[i].tmpdy;
        f[i].ccol = f[i].tmpcol;

        // bouger dans écran circulaire
        f[i].x = (f[i].x + f[i].cdx + SCREENX) % SCREENX;
        f[i].y = (f[i].y + f[i].cdy + SCREENY) % SCREENY;
    }
}
/***********************************************************
affichage
***********************************************************/
void affiche_fourmis(fourmi f[], int mx, int my)
{
    int i;
    // effacer
    al_clear_to_color(NOIR);

    // afficher chaque fourmi
    for (i = 0; i<MAXFOURMI; i++){

        al_draw_filled_rectangle(f[i].x, f[i].y,
            f[i].x + 10, f[i].y + 10,
            f[i].ccol);
    }
    // lignes verticale et horizontales
    al_draw_line(mx, 0, mx, SCREENY, VERT, 2);
    al_draw_line(0, my, SCREENX, my, VERT, 2);
}
/*
changer la couleur
*/
void change_couleur_fourmis(fourmi f[])
{
    int i;
    ALLEGRO_COLOR c[256];
    init_couleurs(c);
    for (i = 0; i<MAXFOURMI; i++)
        f[i].mcol = c[irand(256)];

}
/***********************************************************
tools
***********************************************************/
void init_prog()
{
    if (!al_init())
        erreur("al_init()");

    DISPLAY = al_create_display(SCREENX, SCREENY);
    if (!DISPLAY)
        erreur("al_create_display");

    if (!al_install_keyboard())
        erreur("al_install_keyboard()");

    if (!al_install_mouse())
        erreur("al_install_mouse()");

    if (!al_init_primitives_addon())
        erreur("al_init_primitives_addon()");

    QUEUE = al_create_event_queue();
    if (!QUEUE)
        erreur("al_create_event_queue()");

    TIMER = al_create_timer(1.0 / 60);

    al_register_event_source(QUEUE, al_get_display_event_source(DISPLAY));
    al_register_event_source(QUEUE, al_get_keyboard_event_source());
    al_register_event_source(QUEUE, al_get_mouse_event_source());
    al_register_event_source(QUEUE, al_get_timer_event_source(TIMER));

    al_start_timer(TIMER);
}

void destructeur()
{
    al_destroy_display(DISPLAY);
    al_destroy_event_queue(QUEUE);
    al_destroy_timer(TIMER);
}

void erreur(const char*txt)
{
    ALLEGRO_DISPLAY*d;
    d = al_is_system_installed() ? al_get_current_display() : NULL;
    al_show_native_message_box(d, "ERREUR", txt, NULL, NULL, 0);
    exit(EXIT_FAILURE);
}
/***********************************************************
***********************************************************/

Voici une capture d'écran du programme résultant :

Image non disponible

Obtenir ce livre

Image non disponible

Ce document est une retranscription autorisée du livre Allegro 5 - Programmation de jeux en C et C++ écrit par Frédéric DROUILLON. Initialement publié aux éditions ENI, vous pouvez commander le livre sur le site de l'éditeur.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2015 Frédéric DROUILLON. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.