/**************************************************************************
  CADRE GENERAL DE GESTION DE LISTES D'ACTEURS EN QUANTITE VARIABLE :
  les structures acteurs sont créées et détruites
  selon les besoins du jeu, des sous-programmes gèrent
  automatiquement l'ajout et le retrait d'acteur du terrain de jeu

  Sur cet exemple les acteurs sont des tirs (lasers ou missiles)
  mais le même principe général s'applique
  à des acteurs plus complexes, personnages interactifs, animés...

  Utiliser les touches direction pour déplacer le vaisseau
  Espace pour tirer au laser, entrée pour tirer des missiles
**************************************************************************/

#include <allegro.h>
#include <time.h>


/**********************/
/*     STRUCTURES     */
/**********************/

// chaque acteur qui se déplace
typedef struct acteur
{

    // position du coin sup. gauche
    int x,y;

    // vecteur deplacement
    int dx,dy;

    // largeur hauteur
    int tx,ty;

    // couleur (ne sera plus pertinent avec des sprites importés...)
    int couleur;

    // type (ici 2 types mais on peut en mettre plus):
    //   0 laser
    //   1 missile (accélération horizontale)
    int type;

    // comportement :
    //   0 normal déplacement
    //   1 explosion
    int comportement;
    int cptexplo; // temps depuis l'explosion

    // vivant :
    //   0 mort (doit disparaitre de la liste)
    //   1 vivant
    int vivant;

} t_acteur;

// Une collection de acteurs
typedef struct listeActeurs
{
    // nombre maxi d'éléments
    // =taille du tableau de pointeurs
    int max;

    // nombre effectif de pointeurs utilisés
    // (les autres sont à NULL)
    int n;

    // le tableau de pointeurs (alloué dynamiquement)
    t_acteur **tab;

} t_listeActeurs;


// Spécifique à cet exemple : un vaisseau et une cible

// Un élément à déplacement interactif
typedef struct joueur{
    int x,y;     // position
    int tx,ty;   // taille
    int vit;     // vitesse des déplacements (nombre de pixels)
    int cpttir0; // tempo armement 0
    int cpttir1; // tempo armement 1
    BITMAP *img; // sprite (image chargée)
} t_joueur;

// Un élément à déplacement automatique aléatoire
typedef struct ennemi{
    int x,y;     // position
    int dx,dy;   // vecteur déplacement
    int tx,ty;   // taille
    BITMAP *img; // sprite (image chargée)
} t_ennemi;


/*********************/
/*     PROTOTYPES    */
/*********************/

// Allouer et initialiser un acteur
// pour ce projet il faut x y de départ et type
//   ( à adapter selon besoins )
t_acteur * creerActeur(int x,int y,int type);

// Allouer et initialiser une liste (vide) de acteurs
t_listeActeurs * creerListeActeurs(int maxacteurs);

// Retourne un booléen vrai si il reste de la place
// dans la liste, faux sinon
int libreListeActeurs(t_listeActeurs *la);

// Allouer et ajouter un acteur à la liste
// et retourner l'adresse de ce nouveau acteur
// retourne NULL en cas de problème
// pour ce projet il faut x y de départ et type
//   ( à adapter selon besoins )
t_acteur * ajouterActeur(t_listeActeurs *la,int x,int y,int type);

// Enlever et libèrer un acteur qui était dans la liste en indice i
void enleverActeur(t_listeActeurs *la,int i);



// Actualiser un acteur (bouger ...)
void actualiserActeur(t_acteur *acteur);

// Gérer l'évolution de l'ensemble des acteurs
void actualiserListeActeurs(t_listeActeurs *la);


// Dessiner un acteur sur la bitmap bmp
void dessinerActeur(BITMAP *bmp,t_acteur *acteur);

// Dessiner sur une bitmap l'ensemble des acteurs
void dessinerListeActeurs(BITMAP *bmp,t_listeActeurs *la);


// Un acteur a été touché ou a touché une cible : modifier son état
// ici on indique qu'il passe en comportement 1 (explosion)
// et le vecteur vitesse est divisé en norme (ralentissement)
void destinActeur(t_acteur *acteur);

// Gérer collision (éventuelle) entre un acteur (un tir) et un ennemi
void collisionActeur(t_ennemi *ennemi,t_acteur *acteur);

// Gérer les collisions entre les acteurs (tous les tirs) et un ennemi
void collisionListeActeurs(t_ennemi *ennemi,t_listeActeurs *la);


// Spécifique à cet exemple : gérer le vaisseau et la cible

// Allouer et initialiser joueur
t_joueur * creerJoueur(char *nomimage);

// Actualiser joueur (bouger interactivement et tirer...)
void actualiserJoueur(t_joueur *joueur,t_listeActeurs *la);

// Dessiner joueur sur la bitmap bmp
void dessinerJoueur(BITMAP *bmp,t_joueur *joueur);


// Allouer et initialiser ennemi
t_ennemi * creerEnnemi(char *nomimage);

// Actualiser ennemi (bouger automatiquement au hasard...)
void actualiserEnnemi(t_ennemi *ennemi);

// Dessiner ennemi sur la bitmap bmp
void dessinerEnnemi(BITMAP *bmp,t_ennemi *ennemi);



/******************************************/
/* PROGRAMME PRINCIPAL                    */
/* initialisation puis boucle d'animation */
/******************************************/

int main()
{

    // Buffer
    BITMAP *page;

    // Image de fond
    BITMAP *decor;

    // La collection des acteurs (les tirs)
    t_listeActeurs *acteurs;

    // Le vaisseau manipulé par le joueur
    t_joueur *vaisseau;

    // La cible qui se déplace automatiquement
    t_ennemi *cible;

    // Il y aura du hasard
    srand(time(NULL));

    // Lancer allegro et le mode graphique
    allegro_init();
    install_keyboard();

    set_color_depth(desktop_color_depth());
    if (set_gfx_mode(GFX_AUTODETECT_WINDOWED,800,600,0,0)!=0)
    {
        allegro_message("prb gfx mode");
        allegro_exit();
        exit(EXIT_FAILURE);
    }

    // buffer
    page=create_bitmap(SCREEN_W,SCREEN_H);

    // charger image de fond
    decor=load_bitmap("images/hotplanet.bmp",NULL);
    if (!decor)
    {
        allegro_message("pas pu trouver images/hotplanet.bmp");
        exit(EXIT_FAILURE);
    }

    // créer le vaisseau et la cible
    vaisseau=creerJoueur("images/spaceship.bmp");
    cible=creerEnnemi("images/deathstar.bmp");

    // préparer la liste des acteurs (100 maxi)
    // mais vide initialement
    acteurs=creerListeActeurs(100);

    // BOUCLE DE JEU
    while (!key[KEY_ESC])
    {
        // effacer buffer en appliquant décor  (pas de clear_bitmap)
        blit(decor,page,0,0,0,0,SCREEN_W,SCREEN_H);

        // bouger tout le monde
        actualiserJoueur(vaisseau,acteurs);
        actualiserEnnemi(cible);
        actualiserListeActeurs(acteurs);

        // gérer les collisions
        collisionListeActeurs(cible,acteurs);

        // afficher tout le monde
        dessinerJoueur(page,vaisseau);
        dessinerEnnemi(page,cible);
        dessinerListeActeurs(page,acteurs);

        // afficher tout ça à l'écran
        blit(page,screen,0,0,0,0,SCREEN_W,SCREEN_H);

        // petite temporisation
        rest(10);
    }

    return 0;
}
END_OF_MAIN();



/************************************************/
/*     DEFINITIONS DES SOUS-PROGRAMMES          */
/************************************************/


// Allouer et initialiser un acteur
t_acteur * creerActeur(int x,int y,int type){
    t_acteur *nouv;

    // Allouer
    nouv=(t_acteur *)malloc(1*sizeof(t_acteur));

    // Initialiser ...

    // ici ce qui est commun aux acteurs
    nouv->x=x;
    nouv->y=y;
    nouv->type=type;
    nouv->comportement=0;
    nouv->cptexplo=0; // pas encore explosé mais on initialise par sécurité
    nouv->vivant=1;

    // ici ce qui est spécifique aux types
    switch (type)
    {
        // laser
        case 0:
            nouv->tx=30;
            nouv->ty=5;
            nouv->dx=10;
            nouv->dy=0;
            nouv->couleur=makecol(255,255,0);
            break;

        // missile
        case 1:
            nouv->tx=40;
            nouv->ty=20;
            nouv->dx=2;
            // petite dispersion dans la trajectoire des missiles :
            nouv->dy=rand()%5-2;
            nouv->couleur=makecol(240,220,220);
            break;
    }

    return nouv;
}

// Actualiser un acteur (bouger, sortie écran, fin explosion ...)
void actualiserActeur(t_acteur *acteur){

    // deplacement
    acteur->x=acteur->x+acteur->dx;
    acteur->y=acteur->y+acteur->dy;

    // type missile : accélère en dx mais pas au dela d'une limite
    if (acteur->type==1 && acteur->dx<14)
        acteur->dx++;

    // si dépasse le bord alors disparait
    if (acteur->x+acteur->tx<0 || acteur->x>SCREEN_W || acteur->y+acteur->ty<0 || acteur->y>SCREEN_H )
        acteur->vivant=0;

    // si en cours d'explosion incrémenter cptexplo
    // et si explose depuis trop longtemps alors disparait
    if (acteur->comportement==1)
    {
        acteur->cptexplo++;
        if (acteur->cptexplo > 7)
            acteur->vivant=0;
    }
}

// Dessiner un acteur sur la bitmap bmp
void dessinerActeur(BITMAP *bmp,t_acteur *acteur){
    // Si pas d'explosion
    if (acteur->comportement==0){
        switch(acteur->type)
        {
            case 0:
                rectfill(bmp,acteur->x,acteur->y,acteur->x+acteur->tx,acteur->y+acteur->ty,acteur->couleur);
                break;
            case 1:
                ellipsefill(bmp,acteur->x,acteur->y+acteur->ty/2,2*acteur->tx/3,acteur->ty/2,makecol(255,100,25));
                ellipsefill(bmp,acteur->x,acteur->y+acteur->ty/2,acteur->tx/3,acteur->ty/3,makecol(255,200,50));
                triangle(bmp,acteur->x,acteur->y,acteur->x,acteur->y+acteur->ty,acteur->x+acteur->tx,acteur->y+acteur->ty/2,acteur->couleur);
                break;
        }
    }
    // sinon on dessine l'explosion
    // d'un diamètre décroissant et d'une couleur rougissante
    else {
        switch(acteur->type)
        {
            case 0:
                circlefill(bmp,acteur->x+acteur->tx/2,acteur->y+acteur->ty/2,30-4*acteur->cptexplo,makecol(255,255-15*acteur->cptexplo,255-30*acteur->cptexplo));
                break;
            case 1:
                circlefill(bmp,acteur->x+acteur->tx/2,acteur->y+acteur->ty/2,60-6*acteur->cptexplo,makecol(255-15*acteur->cptexplo,128-15*acteur->cptexplo,0));
                break;
        }
    }
}

// Allouer et initialiser une liste (vide) de acteurs
t_listeActeurs * creerListeActeurs(int maxacteurs){
    t_listeActeurs *nouv;
    int i;

    nouv=(t_listeActeurs *)malloc(1*sizeof(t_listeActeurs));

    nouv->max=maxacteurs;
    nouv->n=0;
    nouv->tab=(t_acteur **)malloc(maxacteurs*sizeof(t_acteur*));

    for (i=0;i<maxacteurs;i++)
        nouv->tab[i]=NULL;

    return nouv;
}

// Retourne un booléen vrai si il reste de la place
// dans la liste, faux sinon
int libreListeActeurs(t_listeActeurs *la){
    return la->n < la->max;
}

// Allouer et ajouter un acteur à la liste
// et retourner l'adresse de ce nouveau acteur
// retourne NULL en cas de problème
// ( mais il vaut mieux d'abord vérifier qu'il
//   y a de la place disponible avant d'appeler )
t_acteur * ajouterActeur(t_listeActeurs *la,int x,int y,int type){
    int i;
    t_acteur *acteur;

    // Liste pleine, on alloue rien et on retourne NULL...
    if (la->n >= la->max)
        return NULL;

    // Allouer un acteur initialisé
    acteur=creerActeur(x,y,type);

    // Chercher un emplacement libre
    i=0;
    while (la->tab[i]!=NULL && i<la->max)
        i++;

    // Si on a trouvé ...
    // (normalement oui car on a vérifié n<max)
    if (i<la->max){
        // Accrocher le acteur à l'emplacement trouvé
        la->tab[i]=acteur;
        la->n++;
    }
    // Sinon c'est qu'il y a un problème de cohérence
    else
        allegro_message("Anomalie gestion ajouterActeur : liste corrompue");

    return acteur;
}

// Enlever et libèrer un acteur qui était dans la liste en indice i
void enleverActeur(t_listeActeurs *la,int i){

    // Vérifier qu'il y a bien un acteur accroché en indice i
    if (la->tab[i]!=NULL)
    {
        // libérer la mémoire du acteur
        free(la->tab[i]);

        // marquer l'emplacement comme libre
        la->tab[i]=NULL;
        la->n--;

    }
}

// Gérer l'évolution de l'ensemble des acteurs
void actualiserListeActeurs(t_listeActeurs *la){
    int i;

    // actualiser chaque acteur
    // si un acteur n'est plus vivant, l'enlever de la liste
    for (i=0;i<la->max;i++)
        if (la->tab[i]!=NULL){
            actualiserActeur(la->tab[i]);
            if (!la->tab[i]->vivant){
                enleverActeur(la,i);
            }
        }
}

// Dessiner sur une bitmap l'ensemble des acteurs
void dessinerListeActeurs(BITMAP *bmp,t_listeActeurs *la){
    int i;

    for (i=0;i<la->max;i++)
        if (la->tab[i]!=NULL)
            dessinerActeur(bmp,la->tab[i]);

}


// Un acteur a été touché ou a touché une cible : modifier son état
// ici on indique qu'il passe en comportement 1 (explosion)
// et le vecteur vitesse est divisé en norme (ralentissement)
void destinActeur(t_acteur *acteur){
    acteur->dx/=2;
    acteur->dy/=2;
    acteur->comportement=1;
    acteur->cptexplo=0;
}

// Gérer collision (éventuelle) entre un acteur (un tir) et un ennemi
void collisionActeur(t_ennemi *ennemi,t_acteur *acteur){
    int vx,vy,d2;

    // si il n'explose déjà pas !
    if ( acteur->comportement==0 )
    {
        // si il est dans la cible alors appeler destinActeur (explosion)

        // calcul du vecteur entre acteur et centre cible
        vx = acteur->x+acteur->tx/2 - (ennemi->x+ennemi->tx/2);
        vy = acteur->y+acteur->ty/2 - (ennemi->y+ennemi->ty/2);

        // calcul distance au carré au centre de la cible (Pythagore)
        // (on reste sur le carré pour éviter de calculer la racine)
        d2 = vx*vx + vy*vy;

        // si dans le disque alors destin...
        if ( d2 < ennemi->tx * ennemi->tx / 4 )
            destinActeur(acteur);
    }
}

// Gérer les collisions entre les acteurs (tous les tirs) et un ennemi
void collisionListeActeurs(t_ennemi *ennemi,t_listeActeurs *la){

    int i;

    // regarder pour chaque acteur...
    for (i=0;i<la->max;i++)
        if (la->tab[i]!=NULL)
            collisionActeur(ennemi,la->tab[i]);

}


// Allouer et initialiser un joueur
t_joueur * creerJoueur(char *nomimage){
    t_joueur *nouv;

    // Allouer
    nouv = (t_joueur *)malloc(1*sizeof(t_joueur));

    // Initialiser

    nouv->img=load_bitmap(nomimage,NULL);
    if (!nouv->img)
    {
        allegro_message("pas pu trouver %s",nomimage);
        exit(EXIT_FAILURE);
    }

    nouv->tx = nouv->img->w;
    nouv->ty = nouv->img->h;

    nouv->x = SCREEN_W/4-nouv->tx/2;
    nouv->y = SCREEN_H/2-nouv->ty/2;
    nouv->vit = 5;

    nouv->cpttir0 = 0;
    nouv->cpttir1 = 0;

    return nouv;
}

// Actualiser joueur (bouger interactivement et tirer...)
void actualiserJoueur(t_joueur *joueur,t_listeActeurs *la){

    // Déplacements instantanés (pas d'inertie)
    // gestion d'un blocage dans une zone écran (moitié gauche)
    if (key[KEY_LEFT]){
        joueur->x -= joueur->vit;
        if (joueur->x<0)
            joueur->x=0;
    }

    if (key[KEY_RIGHT]){
        joueur->x += joueur->vit;
        if (joueur->x+joueur->tx > SCREEN_W/2)
            joueur->x=SCREEN_W/2-joueur->tx;
    }

    if (key[KEY_UP]){
        joueur->y -= joueur->vit;
        if (joueur->y<0)
            joueur->y=0;
    }

    if (key[KEY_DOWN]){
        joueur->y += joueur->vit;
        if (joueur->y+joueur->ty > SCREEN_H)
            joueur->y=SCREEN_H-joueur->ty;
    }

    // Gestion du tir...

    // incrémenter la tempo des tirs
    joueur->cpttir0++;
    joueur->cpttir1++;

    // si le joueur appui sur la gachette et arme ok...
    // espace = laser
    if (key[KEY_SPACE] && joueur->cpttir0>=5){
        ajouterActeur(la,joueur->x+joueur->tx,joueur->y+joueur->ty/2,0);
        joueur->cpttir0 = 0;
    }
    // entrée = missile
    if (key[KEY_ENTER] && joueur->cpttir1>=10){
        ajouterActeur(la,joueur->x+joueur->tx,joueur->y+joueur->ty/2,1);
        joueur->cpttir1 = 0;
    }
}

// Dessiner joueur sur la bitmap bmp
void dessinerJoueur(BITMAP *bmp,t_joueur *joueur){
    draw_sprite(bmp,joueur->img,joueur->x,joueur->y);
}


// Allouer et initialiser ennemi
t_ennemi * creerEnnemi(char *nomimage){
    t_ennemi *nouv;

    // Allouer
    nouv = (t_ennemi *)malloc(1*sizeof(t_ennemi));

    // Initialiser

    nouv->img=load_bitmap(nomimage,NULL);
    if (!nouv->img)
    {
        allegro_message("pas pu trouver %s",nomimage);
        exit(EXIT_FAILURE);
    }

    nouv->tx = nouv->img->w;
    nouv->ty = nouv->img->h;

    nouv->x = 3*SCREEN_W/4-nouv->tx/2;
    nouv->y = SCREEN_H/2-nouv->ty/2;
    nouv->dx=0;
    nouv->dy=0;

    return nouv;
}

// Actualiser ennemi
// (bouger automatiquement au hasard dans la moitié droite...)
void actualiserEnnemi(t_ennemi *ennemi){

    // proba de changement de déplacement : une chance sur 20
    if ( rand()%20==0 ){
        // Nouveau vecteur déplacement
        ennemi->dx = rand()%11-5;
        ennemi->dy = rand()%11-5;
    }

    // contrôle des bords : ici on décide de rebondir sur les bords
    if  (  ( ennemi->x < SCREEN_W/2 && ennemi->dx < 0 ) ||
           ( ennemi->x + ennemi->tx > SCREEN_W && ennemi->dx > 0) )
        ennemi->dx = -ennemi->dx;

    if  (  ( ennemi->y < 0 && ennemi->dy < 0 ) ||
           ( ennemi->y + ennemi->ty > SCREEN_H && ennemi->dy > 0) )
        ennemi->dy = -ennemi->dy;

    // calculer nouvelle position
    // nouvelle position = position actuelle + deplacement
    ennemi->x = ennemi->x + ennemi->dx;
    ennemi->y = ennemi->y + ennemi->dy;

}

// Dessiner ennemi sur la bitmap bmp
void dessinerEnnemi(BITMAP *bmp,t_ennemi *ennemi){
    draw_sprite(bmp,ennemi->img,ennemi->x,ennemi->y);
}