Jeu classique, bataille de tanks


INTRODUCTION

Autre classique, une bataille entre deux … ici deux tanks. Cet exemple est initialement tiré du livre de Jonathan Harbour, Game programming all in one, Thomson course technology, Boston, 2004.

Le jeu

Le jeu se joue à deux. Deux tanks évoluent dans un décor simple composé de rectangles dispersés et de couleurs différentes. L’objectif est d’atteindre son adversaire par des tirs et de marquer des points.

La réalisation

La réalisation du jeu est décomposée en quatre étapes principales. Chaque étape donne lieu à un programme compilé qui fonctionne. A la dernière étape le jeu est terminé :

aaaa1) Réaliser le décor, initialiser et afficher les tanks (sans mouvement, à leurs positions initiales)
aaaa2) Ajouter le mouvement aux tanks, contrôle des collision avec le décor
aaaa3) Les tirs et la gestion des scores
aaaa4) Les explosions lorsqu’un obus touche quelque chose

Modifications possibles, généraliser le modèle du jeu

Bien entendu il est possible de modifier le scénario et de prendre autre chose que des tanks.
La trame sous-jacente tient en effet en cinq aspects assemblés : décor, mouvement contrôlé d’objets, détection et échange d’informations entre les objets, enregistrement des contacts sous la forme d’un affichage libre, mise en scène libre des collisions. A partir de là beaucoup de choses peuvent être imaginées !


1. Première étape : décor, dessin tanks, affichage



aaaTELECHARGER :
aaaTexte des explications, aaaExecutable (Windows), aaaSource (utilise la librairie Allegro)
 


Le choix graphique pour ce programme est de tout dessiner à partir de rectangles de triangles et de cercles.
L’affichage est réalisé avec un double buffer.

Structures de données première partie :

Définir un tank

Le premier point important est de définir un tank. Il a les caractéristiques suivantes :
aaa- une position (x, y)
aaa- une direction parmi quatre possibles : nord 0, est 1, sud 2, ouest 3
aaa- un pas d’avancement
aaa- une ou plusieurs couleurs

Chaque caractéristique est représentée par une variable et l’ensemble des variables est regroupé dans une structure :

aaatypedef struct TANK{
aaaaaaint x,y; aaaaaaa// position du tank
aaaaaaint dir; aaaaaaa// direction : 0 nord, 1 est, 2 sud, 3 ouest
aaaaaaint pas; aaaaaa// pas de l'avancement (et du même coup vitesse)
aaaaaaint c1,c2; aaaa// 2 couleurs
aaa}t_tank;

Fonctions essentielles première partie :

1) Initialisations : encadrer la zone de jeu, créer le décor, poser les tanks


Une fonction pour l’encadrement de la zone de jeu

Une marge de 12 pixels est réservée en haut de l’écran pour d’éventuels affichages et toute la surface restante est entourée d’un cadre de 2 pixels, c’est la zone de jeu. La couleur du cadre est définie par la macro :

aaaa#define COULEUR_CONTOUR aaaamakecol ( 255,242,169 )

La résolution graphique choisie pour le jeu est 8 bits mais le jeu ne crée pas de palette, la palette par défaut est utilisée et quelques couleurs sont créées. Le cadre est tracé une fois pour toute au début du programme directement à l’écran et il ne sera jamais effacé pendant le jeu. Il est tracé de la façon suivante :

aaaavoid init_cadre()
aaaa{
aaaaaaa(1) textprintf(screen,font,1,1,COLOR_CONTOUR,"Bataille chars - %dx%d",SCREEN_W,SCREEN_H);
aaaaaaa(2) rect(screen,0,12,SCREEN_W-1,SCREEN_H-1,COLOR_CONTOUR);
aaaaaaaaaarect(screen,1,13,SCREEN_W-2,SCREEN_H-2,COLOR_CONTOUR);
aaaa}

(1) En haut dans la marge disponible le titre du jeu est affiché ainsi que la résolution écran à l’aide des variables SCREEN_H et SCREEN_W générée par la fonction set_gfx_mode() d’allegro (voir documentation)

(2) Le cadre de 2 pixels est tracé, deux rectangles imbriqués.
Remarque : la taille de l’écran, la bitmap screen, va de 0 compris à SCREEN_W-1 pour la longueur et de 0 compris à SCREEN_H-1 pour la hauteur.


Une fonction pour la création du décor et l’initialisation du double buffer

Le décor consiste en une série de rectangles dessinés au hasard dans la zone de jeu. à l’intérieur de l’encadrement.. Cette zone de jeu va correspondre à la taille du double buffer dont l’affichage sera toujours centré dans l’encadrement

La fonction d'initialisation du décor crée deux bitmaps à la taille de la zone de jeu. L’une est destinée à stocker le décor et l’autre sera le double buffer c'est-à-dire la bitmap d’assemblage de l’ensemble des éléments graphiques (décor, tanks, explosions, bonus etc.)
Cette fonction se présente de la façon suivante :

(1)aaavoid init_decor(BITMAP**deco,BITMAP**buffer)
aaaaa{
aaaaaint i,x,xmin,xmax,y,ymin,ymax,t,c;

(2)aaaaaaa*deco=create_bitmap(SCREEN_W-5,SCREEN_H-17);
aaaaaaaaa *buffer=create_bitmap(SCREEN_W-5,SCREEN_H-17);
aaaaaaaaaaif (!deco || !buffer)
aaaaaaaaaaaa ERREUR("erreur create_bitmap");

aaaaaaaaa clear(*deco);
aaaaaaaaa clear(*buffer);

(3)aaaaaaafor (i=0; i<NB_RECT_DECOR;i++){

(4)aaaaaaaaaax=rand()%( (*deco)->w-TAILLE_RECT_MAX);
aaaaaaaaaaaa y=35+rand()%( (*deco)->h-TAILLE_RECT_MAX-70);
(5)aaaaaaaaaat=8+rand()%(TAILLE_RECT_MAX-7);
aaaaaaaaaaaa // couleur
(6)aaaaaaaaaac=makecol(rand()%255,0,rand()%255);
aaaaaaaaaaaa rectfill(*deco,x,y,x+t,y+t,c);
aaaaaaaaa}
aaaaa}

(1) La fonction init_decor() prend en argument deux pointeurs passés par référence, c'est-à-dire deux pointeurs de pointeur. L’objectif est d’obtenir deux adresses pour des BITMAP* dans le contexte d’appel.

(2) malloc avec contrôle d’erreurs des deux bitmap à l’aide de la fonction create_bitmap() de l’environnement Allegro. Les deux bitmaps sont ensuite initialisés à zéro avec la fonction clear() d’Allegro.

(3) La création du décor consiste à bombarder la bitmap « deco » d’un nombre prédéfini de rectangles :
aaaa#define NBRECT_DECO 20

(4) Pour chaque rectangle une position est déterminée au hasard. Cette position tient compte de la taille maximum prédéfinie d’un rectangle et diminue d’autant les possibilités de positions horizontales :
aaaa#define TAILLE_RECT_MAX 100
Pour la verticale deux couloirs d’une trentaine de pixels sont ménagés en haut et en bas de la zone de jeu en prévision de l’initialisation des tanks l’un en haut et l’autre en bas.

(5) chaque rectangle a également une taille aléatoire dans la fourchette prédéfinie par TAILLE_RECT_MAX avec une taille minimum de 8 pixels

(6) Création d’une couleur aléatoire et affichage final du rectangle à sa position dans la bitmap de stockage du décor « deco »

Une fonction pour l’initialisation des tanks

L’initialisation des tanks consiste à leur attribuer une position initiale, une direction et deux couleurs pour le dessin :

(1)aaaavoid init_tank(BITMAP *bmp,t_tank *t1,t_tank *t2)
aaaaaa{
aaaaaaaaaa// joueur 1 en haut au départ
(2)aaaaaaaat1->x=20+rand()%(bmp->w-40);
aaaaaaa aaat1->y=17;
(3)
aaaaaaaat1->dir=1+rand()%3; // nord interdit
(4)
aaaaaaaat1->pas=0;
(5)
aaaaaaaat1->c1=7; //gris
aaaaaaa aaat1->c2=6; //orange

aaaaaaaaaaa// joueur 2 en bas au départ
(6)
aaaaaaaat2->x=20+rand()%(bmp->w-40);
aaaaaaaaaaat2->y=bmp->h-17;
aaaaaaaaaaat2->dir=rand()%4;
(7)
aaaaaaaaif (t2->dir==2)
aaaaaaaaaaaaaat2->dir+=(rand()%1)*2-1;
aaaaaaaaaaat2->pas=0;
aaaaaaaaaaat2->c1=12; //rouge
aaaaaaaaaaat2->c2=14; //jaune
aaaaaa}

(1) La fonction prend deux tanks passés par référence en paramètre. Elle prend également une bitmap, en fait juste pour avoir sa taille.

(2) Initialisation de la position du premier tank qui est en haut. La position (x, y) d’un tank correspond au centre du tank et non à un coin. Le tank est un carré de 32 fois 32 pixels. La position en x est au plus prés à 4 pixels du bord et en y à 1 pixel.

(3)(7) Initialisation de la direction des tanks. Rappelons qu’il y a quatre directions possibles 0 pour nord, 1 pour est, 2 pour sud et 3 pour ouest. Le tank du haut ne peut pas aller vers le nord et la direction 0 est interdite. Le tank du bas ne peut pas aller vers le sud et c’est la direction 2 qui est interdite

(4) Le pas d’avancement est initialement à zéro et sera modifié avec le mouvement des tanks (voir étape suivante)

(5) Initialisation des couleurs de chaque tank en utilisant des indices de la palette par défaut.

(6) idem 1, 2, 3, 4, 5 mais pour le tank du bas.


2) Affichage des tanks

Nous avons opté ici pour un dessin sommaire de chaque tank avec juste un rectangle et un triangle. Le triangle indique en fait la direction du tank comme une flèche. La fonction de dessin doit tenir compte de la position de chaque tank :

aaaaaa

(1)aaaaaavoid dessine_tank(BITMAP *bmp,t_tank *t)
aaaaaa{
aaaaaaint x=t->x;
aaaaaaint y=t->y;
aaaaaaint dir=t->dir;
aaaaaaint c1=t->c1;
aaaaaaint c2=t->c2;

(2)
aaaaaaaarectfill(bmp, x-16,y-16,x+16,y+16,c1);
(3)
aaaaaaaaswitch(dir){
aaaaaaaaaaaaaa// NORD
aaaaaaaaaaaaaacase 0 :
aaaaaaaaaaaaaaaaatriangle(bmp,x-16,y, x+16,y,x,y-16,c2);
aaaaaaaaaaaaaaaaabreak;
aaaaaaaaaaaaaa// EST
aaaaaaaaaaaaaacase 1 :
aaaaaaaaaaaaaaaaatriangle(bmp,x,y-16, x,y+16,x+16,y,c2);
aaaaaaaaaaaaaaaaabreak;
aaaaaaaaaaaaaa// SUD
aaaaaaaaaaaaaacase 2 :
aaaaaaaaaaaaaaaaatriangle(bmp,x-16,y, x+16,y,x,y+16,c2);
aaaaaaaaaaaaaaaaabreak;
aaaaaaaaaaaaaa//OUEST
aaaaaaaaaaaaaacase 3 :
aaaaaaaaaaaaaaaaatriangle(bmp,x,y-16, x,y+16,x-16,y,c2);
aaaaaaaaaaaaaaaaabreak;
aaaaaaaaaaa}
aaaaaa}

(1) La fonction de dessin des tank prend un tank passé par référence et la bitmap de destination (ce sera le double buffer au moment de l’appel)

(2) Tout d’abord un carré est tracé à partir de la position (x, y) du tank considérée comme le centre.

(3) Un triangle selon son orientation va indiquer la direction nord, est, sud, ouest..


3) Mise en œuvre : librairie première étape, main et boucle d’événements

Il reste à mettre en place le main, et la boucle d’événements.


Librairie préliminaire au jeu

toutes les valeurs prédéfinies, les structures de données ainsi que les déclarations de fonction sont regroupées dans une librairie associée au projet :

aaaaaa#ifndef JEU_TANK
aaaaaa#define JEU_TANK

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

(1)aaa#define ECRAN_X 800
aaaaaa#define ECRAN_Y 600
aaaaaa#define MODE GFX_AUTODETECT_WINDOWED
(2)
aaa#define ERREUR(msg){\
aaaaaaaaaset_gfx_mode(GFX_TEXT,0,0,0,0);\
aaaaaaaaaallegro_message(msg);\
aaaaaaaaaallegro_exit();\
aaaaaaaaaexit(1);\
aaaaaa}
(3)
aaa#define COLOR_CONTOUR makecol(255,242,169)
aaaaaa#define NB_RECT_DECOR 20
aaaaaa#define TAILLE_RECT_MAX 100

aaaaaatypedef struct TANK{
aaaaaaaaaint x,y;aaaaaaaa// position du tank
aaaaaaaaaint dir;aaaaaaaa// direction : 0 nord, 1 est, 2 sud, 3 ouest
aaaaaaaaaint pas;aaaaaaa// pas d'avancement (et du même coup vitesse)
aaaaaaaaaint c1,c2;aaaaa// 2 couleurs
aaaaaa}t_tank;

aaaaaa// fonctions partie 1
aaaaaa// 1. Initialisation
aaaaaavoid init_cadre (void);
aaaaaavoid init_decor (BITMAP **deco, BITMAP **buf);
aaaaaavoid init_tank (BITMAP *bmp,t_tank *t1,t_tank *t2);
aaaaaa// 2. Affichage tanks
aaaaaavoid dessine_tank (BITMAP *bmp,t_tank *t);
aaaaaa#endif

(1) Valeurs prédéfinies pour la résolution de l’écran et l’initialisation graphique. A propos de ECRAN_X et ECRAN_Y cette initialisation effectuée après appel de set_gfx_mode(), ce sont les variables globales SCREEN_W et SCREEN_H qui sont le plus souvent utilisées. MODE prédéfinit le mode graphique sélectionné.

(2) Macro pour le contrôle des erreurs

(3) Les valeurs prédéfinies que nous avons déjà rencontrées ainsi que la définition du type « t_tank » pour les tanks et les déclarations des fonctions vues.

Main et boucle d’événements

aaaaaaint main ()
aaaaaa{
(1)
aaaat_tank t1,t2;
aaaaaa BITMAP *deco,*buf;
aaaaaa int done=0;

(2)aaaaaa aallegro_init();
aaaaaaaaaainstall_keyboard();
aaaaaaaaaasrand(time(NULL));

aaaaaaaaaaif (set_gfx_mode(MODE,ECRAN_X,ECRAN_Y,0,0)!=0)
aaaaaaaaaaaaaERREUR(allegro_error);

(3)aaaaaaaainit_cadre();
aaaaaaaaa ainit_decor(&deco,&buf);
aaaaaaaaaa init_tank(buf,&t1,&t2);

(4)
aaaaaa awhile (!done){

(5)
aaaaaaa aaa blit(deco,buf, 0,0,0,0,deco->w, deco->h);
(6)
aaaaaaaaaaadessine_tank(buf,&t1);
aaaaaaaaaaaaa dessine_tank(buf,&t2);
(7)
aaaaaaaaaaaif (keypressed())
aaaaaaaaaaaaaaaadone=1;
(8)
aaaaaaaaaaablit(buf,screen,0,0,2,14,buf->w,buf->h);
(9)
aaaaaaaaaaarest(30);
aaaaaaaaaa}
aaaaaaaaaareturn 0;
aaaaaa}
aaaaaaEND_OF_MAIN();

(1) Pas de variable globales utilisée dans le programme, initialisation dans le main des variables nécéssaires au programme : les deux tanks, les bitmaps pour le décor et le double buffer. « done » va fournir le test d’arrêt de la boucle d’événements.

(2) Les initialisations d’usage dans l’environnement Allegro (voir tutorial à ce sujet)

(3) Appel des trois fonctions d’initialisation vues plus haut : création du cadre autour de la zone de jeu, initialisation du décor et du double buffer

(4)(7) Boucle d’événements qui prend fin avec la pression d’une touche quelconque.

(5)(6)(8) Mise en place du principe classique des animations :
- effacement du double buffer, réaffichage du décor dans le double buffer, dessins des tanks à leurs positions courantes, et après assemblage de toutes les parties affichage final du double buffer à l’écran avec la fonction blit().

(9) Ralentir le processus avec la fonction rest() en millisecondes


2. Deuxième étape : ajouter le mouvement aux tanks


aaaTELECHARGER :
aaaTexte des explications, aaaExecutable (Windows), aaaSource (utilise la librairie Allegro)

Structures de données seconde partie :

Pas de modification, juste l’ajout dans la librairie d’une valeur prédéfinie pour la vitesse des tanks :
aaa#define VITESSE_TANK 5


Fonctions essentielles seconde partie :

Trois nouveaux domaines de fonctions viennent compléter le socle précédent : mouvements, collisions et contrôle des entrées clavier.

3) Mouvements des tanks

Le mouvement est assuré avec cinq fonctions simples qui ont respectivement pour objectifs de faire avancer, reculer, tourner à gauche, tourner à droite et bouger chacun des tanks.

Avancer

Avancer c’est uniquement incrémenter le pas d’avancement. Si le pas est supérieur à 0 le char avance. Mais c’est dans la fonction bouge() présentée plus bas que le pas est ajouté à la position (x,y) du char en fonction de sa direction. Le pas d’avancement définit aussi la vitesse du char et les fonctions avance() et recul() vont permettre également de contrôler la vitesse du tank en modifaint le pas d’avancement.

aaaaavoid avance (t_tank *t)
aaaaa{
aaaaaaaaat->pas=(++t->pas>VITESSE_TANK) ? VITESSE_TANK : t->pas;
aaaaa}

Juste une ligne pour l’incrémentation du champ « pas » de façon à ce qu’il ne dépasse pas la vitesse maximum fixée par la valeur prédéfinie VITESSE_TANK

Reculer

La fonction de recul est identique à celle pour avancer si ce n’est qu’elle décrémente le « pas » au lieu de l’incrémenter et avec pour limite négative –VITESSE_TANK

aaaaavoid recul (t_tank *t)
aaaaa{
aaaaaaaaat->pas=(--t->pas<-VITESSE_TANK) ? -VITESSE_TANK : t->pas;
aaaaa}

Tourner à gauche

Tourner revient à modifier la variable de direction « dir ». Tourner à gauche décrémente la variable afin que ses valeurs remontent dans le sens inverse des aiguilles d’une montre, de 0 à 3 à 2 à 1. Afin de contrôler le passage de 0 à 3 il est nécessaire d’ajouter la valeur 4 avant le modulo 4 sachant que : (0+4)%4 donne 0 et que (-1+4)%4 donne 3.

aaaaavoid tourne_gauche (t_tank *t)
aaaaa{
aaaaaaaaat->dir= ((t->dir-1)+4)%4;
aaaaa}

Tourner à droite

Tourner à droite incrémente la variable afin que ses valeurs descendent dans le sens des aiguilles d’une montre, de 0 à 1 à 2 à 3. Un modulo suffit à contrôlerle passage de 3 à 0.

aaaaavoid tourne_droite (t_tank *t)
aaaaa{
aaaaaaaaat->dir= (t->dir+1)%4;
aaaaa}

Bouger

Quelque soit la valeur du pas d’avancement elle est ajoutée à la position (x, y) en fonction de la direction du char. Selon les touches appuyées (voire plus bas le contrôle du clavier) le char avance ou recule dans une direction ou une autre. Mais la fonction bouge() est appelée de façon permanente dans la boucle d’évènement et dés lors qu’il est mobile avec un pas d’avancement différent de 0 le char se déplace de façon continue. Par ailleurs la fonction bouge n’opère aucun contrôle des collisions. Les collisions sont contrôlées avec la fonction percute() présentée plus bas.

aaaaavoid bouge(t_tank *t)
aaaaa{
aaaaaint dir=t->dir;
aaaaaint pas=t->pas;
aaaaaaaaaswitch(dir){
aaaaaaaaaaaaacase 0 : t->y-=pas; break;
aaaaaaaaaaaaacase 1 : t->x+=pas; break;
aaaaaaaaaaaaacase 2 : t->y+=pas; break;
aaaaaaaaaaaaacase 3 : t->x-=pas; break;
aaaaaaaaa}
aaaaa}

Vers le nord « pas » est décrémenté de y, vers l’est « pas » est ajouté à x, vers le sud « pas » est ajouté en y et vers l’ouest « pas » est soustrait en x.

4) Collisions

Une seule fonction contrôle les collisions des tanks avec les limites de la zone de jeu et les éléments du décor. La détection des collisions teste pour des points de pare choc des tanks la couleur de fond correspondante, si elle est noire, couleur du fond, il n’y a rien sinon le tank touche quelque chose et dans ce cas il s’arrête. le contrôle des collisions s’effectue dans la boucle d’évènement avant le déplacement c'est-à-dire avant appel à la fonction bouge() vue précédemment. En effet, en cas de collision le pas d’avancement est mis à zéro ce qui a pour effet d’immobiliser le char.

(1)aaavoid percute( BITMAP *bmp, t_tank *t)
aaaaa{
aaaaaint dir=t->dir;
aaaaaint pas=t->pas;
aaaaaint x=t->x;
aaaaaint y=t->y;
aaaaaint x1,y1,x2,y2,x3,y3;
aaaaaint choc=17+ABS(t->pas);

(2)
aaaaaaif (pas!=0){

(3)aaaaaaaaaaif (pas>0){
aaaaaaaaaaaaaaaaaswitch(dir){
aaaaaaaaaaaaaaaaaaaaa//chocs nord
aaaaaaaaaaaaaaaaaaaaacase 0 : x1=x-16; x2=x; x3=x+16; y1=y2=y3=y-choc; break;
aaaaaaaaaaaaaaaaaaaaa//chocs est
aaaaaaaaaaaaaaaaaaaaacase 1 : x1=x2=x3=x+choc; y1=y-16; y2=y; y3=y+16; break;
aaaaaaaaaaaaaaaaaaaaa//chocs sud
aaaaaaaaaaaaaaaaaaaaacase 2 : x1=x-16; x2=x; x3=x+16; y1=y2=y3=y+choc; break;
aaaaaaaaaaaaaaaaaaaaa//chocs ouest
aaaaaaaaaaaaaaaaaaaaacase 3 : x1=x2=x3=x-choc; y1=y-16; y2=y; y3=y+16; break ;
aaaaaaaaaaaaaaaa}
aaaaaaaaaaaa}
(4)aaaaaaaaaelse if (pas<0){
aaaaaaaaaaaaaaaaswitch(dir){
aaaaaaaaaaaaaaaaaaaaa//chocs sud
aaaaaaaaaaaaaaaaaaaaacase 0 : x1=x-16; x2=x; x3=x+16; y1=y2=y3=y+choc; break;
aaaaaaaaaaaaaaaaaaaaa//chocs ouest
aaaaaaaaaaaaaaaaaaaaacase 1 : x1=x2=x3=x-choc; y1=y-16; y2=y; y3=y+16; break;
aaaaaaaaaaaaaaaaaaaaa//chocs nord
aaaaaaaaaaaaaaaaaaaaacase 2 : x1=x-16; x2=x; x3=x+16; y1=y2=y3=y-choc; break;
aaaaaaaaaaaaaaaaaaaaa//chocs est
aaaaaaaaaaaaaaaaaaaaacase 3 : x1=x2=x3=x+choc; y1=y-16; y2=y; y3=y+16; break;
aaaaaaaaaaaaaaaa}
aaaaaaaaaaaa}
(5)aaaaaaaaaif ( getpixel(bmp,x1,y1)||getpixel(bmp,x2,y2)||getpixel(bmp,x3,y3))
aaaaaaaaaaaaaaaat->pas=0;
aaaaaaaa}
aaaaa}

(1) La fonction percute() prend en paramètre une bitmap, le double buffer sera passé en argument au moment où il contient juste le décor (voir la mise en œuvre plus bas). C’est d’ailleurs discutable car du coup les tanks ne peuvent pas se détecter mutuellement. Le second paramètre est le tank concerné par la recherche de collision avec le décor.
Vient ensuite une collection de variables locales. « dir, pas x et y » reprennent des valeurs de la structure tank et sont employées pour faciliter l’écriture du code ensuite. « x1,y1, x2,y2,x3,y3 » sont les variables du pare chocs : trois points pris à la limite du tank selon sa direction et s’il avance ou recule ; en blanc les points s’il avance et en noir s’il recule :

aaaaaa

La dernière variable « choc » stocke la distance du centre du tank au pare chocs plus 1 + le pas d'avancement.

(2) Les instructions ne sont effectuées que si le tank est mobile.

(3) Si le tank est mobile alors soit il avance soit il recule. Il avance si le pas d’avancement est supérieur à 0. Les points de pare chocs prennent alors leurs valeurs en fonction de la direction du tank.

(4) Le tank recule si le pas est inférieur à 0. Les points du pare choc sont alors inversés en fonction des direction (nord donne pare choc sud, est donne pare choc ouest etc.)

(5) Finalement le test sur les trois points de pare chocs concernés. Si dans la bitmap une valeur différente de 0, il y a quelque chose et le tank est arrêté.

5) Contrôle clavier

Les actions sont commandées avec le clavier et une fonction unique regroupe tous les appels selon les touches appuyées. Le clavier est approché via les scancode (KEY_UP, KEY_DOWN, KEY_RIGHT, etc.) qui correspondent à des indice du tableau « key » de l’environnement Allegro. Ce tableau réactualisé en permanence de façon invisible dans le code et à chaque indice est affecté 0 pour une touche non appuyée et 1 lorsqu’elle est appuyée. Ainsi un test comme « if (key[KEY_UP]) » permet de savoir si la touche flèche haut est appuyée ou non.

(1)aaaaaint entree_clavier(t_tank *t1, t_tank *t2)
aaaaa{
aaaaaint done=0;
(2)
aastatic int A=0;
aaaaastatic int S=0;
aaaaastatic int L=0;
aaaaastatic int R=0;
aaaaa#define GAUCHE 0
aaaaa#define DROITE 1
aaaaa#define TOURNE(K,flag,DIR,tk) {\
aaaaaaaaaif (K && !(flag)){\
aaaaaaaaaaaaaflag=1;\
aaaaaaaaaaaaaif (DIR==GAUCHE)\
aaaaaaaaaaaaaaaaatourne_gauche(tk);\
aaaaaaaaaaaaaelse\
aaaaaaaaaaaaaaaaatourne_droite(tk);\
aaaaaaaaa}\
aaaaaaaaaelse if (!K)\
aaaaaaaaaaaaaflag=0;\
aaaaa}

(3)
aaaaaa// JOUEUR 1
aaaaaaaaaif (key[KEY_Z])
aaaaaaaaaaaaarecul(t1);
aaaaaaaaaif (key[KEY_W])
aaaaaaaaaaaaaavance(t1);

aaaaaaaaaTOURNE(key[KEY_A],A,GAUCHE,t1);
aaaaaaaaaTOURNE(key[KEY_S],S,DROITE,t1);

(4)
aaaaaa//JOUEUR 2
aaaaaaaaaif (key[KEY_UP])
aaaaaaaaaaaaaavance(t2);
aaaaaaaaaif (key[KEY_DOWN])
aaaaaaaaaaaaarecul(t2);

aaaaaaaaaTOURNE(key[KEY_LEFT],L,GAUCHE,t2);
aaaaaaaaaTOURNE(key[KEY_RIGHT],R,DROITE,t2);

(5)
aaaaaaaif (key[KEY_ESC])
aaaaaaaaaaaaadone=1;
aaaaaaaaareturn done;
aaaaa}

(1) La fonction prend en argument chacun des deux tanks qui seront nécessaires selon les fonctions appelées avec l’appuie des touches. La fonction renvoie une valeur pour la touche de fin du programme, c’est la valeur de la variable « done » initialisée par défaut à 0 .

(2) L’utilisation des scancodes pour le contrôle du clavier présente l’intérêt de ne pas bloquer le processeur dans l’attente qu’une touche soit appuyée comme le fait par exemple la fonction readkey(). C’est ce qui permet d’avoir plusieurs joueurs simultanément dans le jeu. En revanche cela peut parfois poser le problème d’une trop rapide répétition des touches et nécessiter un contrôle spécifique si l’on ne veut pas avoir de répétition. Le principe de ce contrôle est simple : attendre que la touche repasse à 0 avant de considérer qu’elle peut à nouveau déclencher une commande. C’est dans cette perspective que sont utilisées des variables statiques. Chaque touche concernée est doublée d’une variable « static » qui ne repasse à 0 que si la valeur pour la touche repasse également à 0 et pour envoyer une commande il faut à la fois que la variable et la touche correspondent à des valeurs de 0.
A cet effet sont utilisées quatre variables dont les valeurs vont doubler celles des touches pour les actions de tourner, ce sont les touches A et S pour le tank 1 doublées par les variables A et S et les touches flèches gauche et droite pour le tank 2 doublées des variables L et R.
Deux valeurs prédéfinies sont également utilisées, GAUCHE pour gauche et DROITE pour droite. Enfin la macro TOURNE() permet de généraliser le contrôle qui a lieu pour chacun des deux tanks. Ses arguments font l’objet des remplacements suivants : « K » est la touche, « flag » le booléen (une des variables A,S, L ouR), DIR est GAUCHE ou DROITE, « tk » correspond au tank concerné.
Le test est simple : si la touche est appuyée et que le flag est à 0 le flag passe à 1 et la fonction correspondante est appelée. Sinon, seulement lorsque la touche repasse à 0 le flag est lui aussi remis à 0.

(3)(4) Les différents tests selon les touches avec appels ou non des fonctions correspondantes.

(5) Si la touche échappe est pressée la variable « done » qui fait l’objet du retour de la fonction passe à 1.

Mise en œuvre : compléter librairie et boucle d’événements

Librairie

Comme vu précédemment ajouter dans la librairie la valeur prédéfinie pour la vitesse des tanks :

aaaaa#define VITESSE_TANK 5

ainsi que les déclarations des fonctions :

aaaaa// fonctions part 2
aaaaa// 3. Mouvements des tanks
aaaaavoid avance (t_tank *t);
aaaaavoid recul (t_tank *t);
aaaaavoid tourne_gauche (t_tank *t);
aaaaavoid tourne_droite (t_tank *t);
aaaaavoid bouge (t_tank *t);
aaaaa// 4. Collisions
aaaaavoid percute (BITMAP *bmp,t_tank *t);
aaaaa// 5. Entrées claviers
aaaaaint entree_clavier (t_tank *t1, t_tank *t2);

Boucle d’événements

La mise à jour de la boucle d’évènements, ajouter les appels en gras des nouvelles fonctions vues précédemment :

aaaaawhile (!done){

aaaaablit(deco,buf, 0,0,0,0,deco->w, deco->h);

aaaaapercute(buf,&t1);
aaaaapercute(buf,&t2);
aaaaabouge(&t1);
aaaaabouge(&t2);

aaaaadessine_tank(buf,&t1);
aaaaadessine_tank(buf,&t2);
aaaaaif (keypressed())
aaaaaaaaadone=entree_clavier(&t1,&t2);
aaaaablit(buf,screen,0,0,2,14,buf->w,buf->h);
aaaaarest(30);
}


3. Troisième étape : ajouter les tirs et les scores


aaaTELECHARGER :
aaaTexte des explications, aaaExecutable (Windows), aaaSource (utilise la librairie Allegro)


Structures de données troisième partie :

Modification :

1) Chaque tank a deux caractéristiques supplémentaires qui complètent la structure t_tank :
aaaa- un boulet pour le tir
aaaa- son score

Le score est un entier mais les boulets ont eux-mêmes plusieurs caractéristiques qui font l’objet d’une structure intégrée ensuite à celle des tanks.

2) Caractéristiques des boulets :
aaaa- une position x,y
aaaa- le boulet est actif ou non
aaaa- le pas d’avancement du boulet en x et y

Détails structure boulet

La structure boulet a en fait 6 rubriques qui sont définis comme suit :

aaaatypedef struct BOULET{
(1)
aaaaaint x,y;
(2)
aaaaaint actif;
(3)
aaaaaint pasx;
aaaaaaaaint pasy;
(4)
aaaaaint c;
aaaaaaaaint r;
aaaa}t_boule;

(1) La position horizontale et verticale u boulet

(2) Chaque tank dispose d’un seul boulet pour tirer. Lorsqu’un tank tire le boulet s’en va et le tank doit attendre qu’il ait terminé sa trajectoire avant de pouvoir tirer une seconde fois.
En d’autres termes il s’agit de savoir si si le boulet est actif ou non pour pouvoir tirer, c’est le rôle de la variable « actif » qui va prendre les valeurs 0 si non et de 1 si oui.

(3) La direction du boulet est donnée en fait au moment du tir, selon la position du tank avec les deux pas d’avancement pasx et pasy.

(4) L’affichage du boulet, comme pour les tanks, est un dessin et il n’y a pas d’utilisation d’images sprites dans ce programme. La variable « c » donne la couleur du boulet et « r » donne le rayon du boulet. En effet les boulets sont représentés par des cerceaux de taille différentes, comme des sortes de soucoupes.
Modification structure tank
Une fois la structure du boulet établie, il n’a y plus qu’à ajouter une structure boulet dans la structure tank et un entier pour stocker le score de chacun des deux tanks. Le type t_tank devient :

aaaatypedef struct TANK{
aaaaaaaaint x,y;aaaaaaa aa// position du tank
aaaaaaaaint dir;aaaaaaaaaa// direction : 0 nord, 1 est, 2 sud, 3 ouest
aaaaaaaaint pas;aaaaaaaaa// pas d'avancement (et du même coup vitesse)
aaaaaaaaint c1,c2;aaaaaaa// 2 couleurs
aaaaaaaat_boule boule;aaa// 1 boulet
aaaaaaaaint score;aaaaaaa// total points du char
aaaa}t_tank;


Fonctions essentielles troisième partie :

Modifications :
Hormis le changement dans la boucle d’événements présentées plus bas, trois fonctions vues précédemment font l’objet de
modifications :

1) Initialisation des tanks

Au moment de l’initialisation des tanks avec la fonction init_tank() une ligne est rajoutée qui « désactive » les boulets de chacun des deux tanks :

aaaavoid init_tank(BITMAP *bmp,t_tank *t1,t_tank *t2){
aaaaaaaa(…)
aaaaaaaat1->boule.actif=0 ;
aaaaaaaa(…)
aaaaaaaat2->boule.actif=0 ;
aaaaaaaa(…)
aaaa}

2) Affichage des tanks

Le score de chaque tank est affiché directement sur chaque tank et fait parti du dessin. Au moment de l’affichage des tanks deux lignes, la première pour définir le mode d’affichage du texte paramétré pour un fond transparent et la seconde qui affiche le score :

aaaavoid dessine_tank(BITMAP *bmp,t_tank *t){
aaaaaaaa(…)
aaaaaaaatext_mode(-1);
aaaaaaaatextprintf(bmp,font,x-4,y-4,COLOR_VERT,"%d",t->score);
aaaa}

5) Contrôle clavier

Les tirs sont concrétisés à l’aide d’un appel à la fonction tir() qui est détaillée plus bas. Cette fonction est appelée avec pression de la touche espace pour le tank 1 et de la touche enter pour le tank 2. Les lignes suivantes sont ainsi à ajouter dans la fonction :

aaaaint entree_clavier(BITMAP *bmp,t_tank *t1, t_tank *t2){
aaaaaaaa(…)
aaaaaaaaif (key[KEY_SPACE])
aaaaaaaaaaaatir(bmp,t1);
aaaaaaaa(…)
aaaaaaaaif (key[KEY_ENTER])
aaaaaaaaaaaatir(bmp,t2);
aaaaaaaa(…)
aaaa}

Nouveautés :
Trois nouvelles fonctions sont ajoutées qui concernent tir, contrôle du tir et contrôle du score.

6) Tirs et boulets

Le tir

Au moment de la pression des touches ESPACE ou ENTER la fonction tir() est appelée pour le tank concerné. Essentiellement cette fonction initialise la structure boulet du tank si le boulet est disponible. L’initialisation du boulet se fait en fonction de la direction du tank qui tire toujours droit devant lui.

(1)aaaavoid tir(BITMAP *bmp,t_tank *t)
aaaa{
aaaaint x=t->x;
aaaaint y=t->y;
aaaaint dir=t->dir;
aaaaint r,c;

(2)aaaaaaaif ( ! t->boule.actif){
aaaaaaaaaaaaat->boule.actif=1;
aaaaaaaaaaaaat->boule.r=2+rand()%10;
aaaaaaaaaaaaar=t->boule.r;
aaaaaaaaaaaaat->boule.c=makecol(rand()%255,rand()%255,rand()%255);

(3)aaaaaaaaaaswitch(dir){
aaaaaaaaaaaaaaaacase 0 :
aaaaaaaaaaaaaaaaaaaat->boule.x=x;
aaaaaaaaaaaaaaaaaaaat->boule.y=y-18-r;
aaaaaaaaaaaaaaaaaaaat->boule.pasx=0;
aaaaaaaaaaaaaaaaaaaat->boule.pasy=-VITESSE_BOULET;
aaaaaaaaaaaaaaaaaaaabreak;

aaaaaaaaaaaaaaaacase 1 :
aaaaaaaaaaaaaaaaaaaat->boule.x=x+18+r;
aaaaaaaaaaaaaaaaaaaat->boule.y=y;
aaaaaaaaaaaaaaaaaaaat->boule.pasx=VITESSE_BOULET;
aaaaaaaaaaaaaaaaaaaat->boule.pasy=0;
aaaaaaaaaaaaaaaaaaaabreak;

aaaaaaaaaaaaaaaacase 2 :
aaaaaaaaaaaaaaaaaaaat->boule.x=x;
aaaaaaaaaaaaaaaaaaaat->boule.y=y+18+r;
aaaaaaaaaaaaaaaaaaaat->boule.pasx=0;
aaaaaaaaaaaaaaaaaaaat->boule.pasy=VITESSE_BOULET;
aaaaaaaaaaaaaaaaaaaabreak;

aaaaaaaaaaaaaaaacase 3 :
aaaaaaaaaaaaaaaaaaaat->boule.x=x-18-r;
aaaaaaaaaaaaaaaaaaaat->boule.y=y;
aaaaaaaaaaaaaaaaaaaat->boule.pasx=-VITESSE_BOULET;
aaaaaaaaaaaaaaaaaaaat->boule.pasy=0;
aaaaaaaaaaaaaaaaaaaabreak;
aaaaaaaaaaaa}
aaaaaaaa}
aaaa}

(1) La fonction prend comme paramètres une bitmap, le double buffer, et un tank. Elle ne renvoie rien. Les variables x,y et dir prennent le valeurs respectives du tank concerné et sont uniquement présentes pour faciliter l’écriture.

(2) Le tir n’est possible que si le boulet est inactif. Si tel est le cas, le boulet devient actif, le rayon du cercle qui va représenter le boulet est donné aléatoirement compris entre les valeurs 2 et 11 (la variable « r » va faciliter l’écriture ensuite) et pour finir une couleur aléatoire est donnée au boulet.

(3) La structure boulet du tank concerné est initialisée en fonction de la direction du tank (nord 0, est 1, sud 2, ouest 3). La position (x, y) initiale du boulet est calculée à partir de la position du tank et rappelons que la position du tank correspond au centre du tank. La position du boulet, selon la direction du tank est décalée de 18 pixels par rapport à celle du tank (le tank est un carré de 32x32 pixels et sa position est à 16 pixels des bords). Ce décalage est augmenté de la taille du rayon du boulet obtenu juste avant le switch. La position du boulet, comme pour le tank, correspond également au centre du boulet.
Ensuite la trajectoire du boulet ainsi que sa vitesse sont initialisées avec l’affection d’une valeur aux variables pasx et pasy du boulet. La vitesse correspond à une valeur prédéfinie VITESSE_BOULET et la direction du boulet dépend de la direction du tank ; vers le nord pas de déplacement en x et décrémentation en y, vers l’est ajout du pas en x et rien en y etc.
Contrôle de la trajectoire du boulet actif
Une fois le tir déclenché, le boulet est parti, sa position varie selon sa trajectoire et il peut toucher quelque chose. Ce contrôle est effectué dans la boucle d’évènement avec la fonction suivante :

(1)aaaaint cntl_tir(BITMAP *bmp,t_tank *t1,t_tank *t2)
aaaa{
aaaaint x=t1->boule.x;
aaaaint y=t1->boule.y;
aaaaint r=t1->boule.r;
aaaaint c=t1->boule.c;
aaaaint res=0;

(2)aaaaaif(t1->boule.actif){
aaaaaaaaaaax=(t1->boule.x+=t1->boule.pasx);
aaaaaaaaaaay=(t1->boule.y+=t1->boule.pasy);

(3)aaaaaaaaif (x<r || y<r || x>bmp->w-r || y>bmp->h-r )
aaaaaaaaaaaaaat1->boule.actif=0;
aaaaaaaaaaelse if ( getpixel(bmp,x,y-r) || getpixel(bmp,x+r,y) || getpixel(bmp,x,y+r) || getpixel(bmp,x-r,y) ){
aaaaaaaaaaaaaat1->boule.actif=0;
aaaaaaaaaaaaaacntl_score(t1,t2);
aaaaaaaaaaaaaares=1;
aaaaaaaaaa}
(4)aaaaaaaelse
aaaaaaaaaaaaacircle(bmp,x,y,r,c);
aaaaaa}
(5)aaaareturn res;
aaaa}

(1) La fonction a trois paramètres, la bitmap qui sera le double buffer puis chacun des deux tanks. Elle renvoie une valeur de 1 lorsque l’obus touche quelque chose et 0 sinon, cette fonctionnalité permettra de déclencher des explosions plus tard.

(2) Dans le cas où le boulet n’est pas actif rien ne se passe mais si oui alors la future position du boulet est stockée dans les variable x et y locales de la fonction.

(3) Ensuite teste pour savoir si le boulet sort de la bitmap (le double buffer qui représente la zone de jeu), si oui le boulet est désactivé. Mais si le boulet est dans la zone de jeu un autre teste est fait pour voir s’il touche quelque chose. Dans ce cas aussi il est désactivé et sa course s’arrête, la variable « res » qui donne la valeur du retour de la fonction est mise à 1 et il reste à savoir si le tank adverse a été touché où non avec appel de la fonction cntl_score() qui se charge de cette évaluation ainsi que de la mise à jour éventuelle des scores.

(4) Si le boulet ne sort pas de la zone de jeu et ne touche rien il continue simplement sa course et est affiché à sa nouvelle position.

(5) A l’issue de la fonction la variable « res » initialisée par défaut à 0 est renvoyée pour indiquer si l’obus a touché ou pas quelquechose.


7) Compte des scores

La fonction cntl_score() évalue si le boulet d’un tank touche ou non le tank adverse et si oui incrémente le score du tireur.

(1)aaaavoid cntl_score(t_tank *t1,t_tank *t2)
aaaa{
aaaaint x=t1->boule.x;
aaaaint y=t1->boule.y;
aaaaint r=t1->boule.r;
aaaaint dx,dy;


(2)aaaaa dx=ABS(t2->x-x);
aaaaaaaady=ABS(t2->y-y);
aaaaaaaaif (dx<=16+r && dy<=16+r)
aaaaaaaaaaaat1->score++;
aaaa}

(1) La fonction prend en paramètre deux tanks, le premier est le tank tireur le second le tank adverse peut-être touché.
Pour faciliter l’écriture des variables locales récupèrent la position ainsi que le rayon du boulet du tank tireur.

(2) Si les distances en x et y du boulet du tank 1 au tank 2 adverse sont inférieures chacune à 16 (le centre du tank) plus le rayon de l’obus alors il y a collision entre l’obus et le tank adverse et le score est augmenté de 1. Sur le shéma ci-dessous il n’y a pas collision car la distance horizontale « dx » est supérieure à la moitié de la longueur du carré plus le rayon du cercle :

aaaaa

Mise en œuvre : compléter librairie et boucle d’événements

Librairie

Comme vu précédemment les lignes suivantes sont ajoutées dans la librairie :

aaaatypedef struct BOULET{
aaaaaaaaint x,y;
aaaaaaaaint actif;
aaaaaaaaint pasx;
aaaaaaaaint pasy;
aaaaaaaaint c;
aaaaaaaaint r;
aaaa}t_boule;

aaaatypedef struct TANK{
aaaaaaaa(…)
aaaaaaaat_boule boule;
aaaaaaaaint score;
aaaa}t_tank;

aaaa(…)
aaaa// FONCTIONS PART 3

aaaa// 6. Tirs et boulets
aaaavoid tir (BITMAP *bmp,t_tank *t);
aaaaint cntl_tir (BITMAP *bmp,t_tank *t1, t_tank *t2);

aaaa// 7. Controle des scores
aaaavoid cntl_score (t_tank *t1,t_tank *t2);

aaaa
Boucle d’évènements

Les nouvelles fonctions appelées vues précédemment sont ajoutées en gras :

aaaawhile (!done){

aaaaaaaablit(deco,buf, 0,0,0,0,deco->w, deco->h);
aaaaaaaapercute(buf,&t1);
aaaaaaaapercute(buf,&t2);
aaaaaaaabouge(&t1);
aaaaaaaabouge(&t2);
aaaaaaaadessine_tank(buf,&t1);
aaaaaaaadessine_tank(buf,&t2);

aaaaaaaacntl_tir(buf,&t1,&t2);
aaaaaaaacntl_tir(buf,&t2,&t1);


aaaaaaaaif (keypressed())
aaaaaaaaaaadone=entree_clavier(buf,&t1,&t2);
aaaaaaaablit(buf,screen,0,0,2,14,buf->w,buf->h);
aaaaaaaarest(30);
aaaa}

4. Quatrième étape : ajouter les explosions


aaaTELECHARGER :
aaaTexte des explications, aaaExecutable (Windows), aaaSource (utilise la librairie Allegro)


Les explosions sont représentées par des cercles de rayons et de couleurs différentes où des carrés construits avec le rayon comme segment. Ils partent dans plein de directions puis diminuent progressivement de taille avant de disparaître.

Structures de données troisième partie :

Modification :

Ces cercles ou carrés reprennent les caractéristiques des boulets et ils sont stockés dans un tableau de boulets. Ce tableau constitue la matière d’une explosion, en quelque sorte le nombre des éclats de l’explosion. Comme il y a deux tanks et que deux explosions peuvent avoir lieu simultanément, il y a deux tableaux de boulets, un pour les explosions possibles des boulets de chaque tank. La taille de ces tableaux correspond au nombre des éclats d’une explosion est prédéfini par une macro :

aaaa#define TAB_EXPLOSION 15

Les deux tableaux sont déclarés dans le main() :

aaaat_boule tab1[TAB_EXPLOSION] ;
aaaat_boule tab2[TAB_EXPLOSION] ;


Fonctions essentielles troisième partie :

Nouveauté :

En ce qui concerne les fonctions il n’y a aucune modification des fonctions existantes et trois nouvelles fonctions sont ajoutées. La première fonction initialise au départ les deux tableaux avec des valeurs de 0. La seconde initialise tous les éclats de l’explosion lorsqu’un obus touche quelque chose et la troisième contrôle dans la boucle d’évènements le déroulement de l’explosion, c'est-à-dire la trajectoire des éclats.

8. Explosions

Initialisation des tableaux


Avant la boucle d’événements les tableaux sont initialisés avec des valeurs de 0 et tous les boulets sont marqués comme inactifs, ce sont deux appels à la fonction standart memset() :

aaaavoid init_explosion(t_boule *tab1,t_boule *tab2)
aaaa{
aaaaaaaamemset(tab1,0,sizeof(t_boule)*TAB_EXPLOSION);
aaaaaaaamemset(tab2,0,sizeof(t_boule)*TAB_EXPLOSION);
aaaa}


Armer l’explosion

Armer l’explosion consiste à initialiser toutes les structures t_boule qui sont libres dans un des deux tableaux. Comme l’explosion a lieu à la position d’un obus qui touche quelque chose, le tank tireur doit être communiqué à la fonction afin qu’elle dispose des informations de position du boulet.

(1)aaaavoid arme_explosion(t_tank *t, t_boule *tab)
aaaaaa{
aaaaaaint i;

(2)aaaaaaafor (i=0; i<TAB_EXPLOSION;i++)
aaaaaaaaaaaaaif (tab[i].actif==0){
aaaaaaaaaaaaaaaaatab[i].actif=1+rand()%1;
aaaaaaaaaaaaaaaaatab[i].x=t->boule.x;
aaaaaaaaaaaaaaaaatab[i].y=t->boule.y;
aaaaaaaaaaaaaaaaatab[i].r=2+rand()%3;
aaaaaaaaaaaaaaaaatab[i].pasx= (rand()%VITESSE_BOULET*2)-VITESSE_BOULET;
aaaaaaaaaaaaaaaaatab[i].pasy= (rand()%VITESSE_BOULET*2)-VITESSE_BOULET;
aaaaaaaaaaaaa}
aaaaaa}

(1) La fonction ne renvoie rien et prend un tank et un tableau d’éclats en argument.

(2) Toutes les position du tableau sont passées en revue et chaque éclat inactif est initialisé. En premier il devient actif. Les éclats pendant l’explosion durent plus ou moins longtemps et le champ « actif » code non seulement le fait que l’éclat est actif mais également la durée de son activité (ce mécanisme est présenté dans la fonction suivante de contrôle des explosions). Au départ tous les éclats ont la même position qui est celle du boulet. En revanche ils n’ont ni les même trajectoires ni les mêmes vitesse de déplacement. La vitesse maximum des éclats est définie par la macro :
aaaa#define VITESSE_BOULET 15

Suivi d’explosion

Le contrôle du mouvement des éclats est effectué dans la boucle d’événement
avec la fonction suivante :

(1)aaaavoid cntl_explosion(BITMAP *bmp, t_boule *tab)
aaaaaa{
aaaaaaaint i,r,x,y;

aaaaaaaaaaafor (i=0;i<TAB_EXPLOSION;i++){
(2)aaaaaaaaaaaaif (tab[i].actif>0){
aaaaaaaaaaaaaaaaaa// d'abord temps de mouvement du cercle lié à la variable actif
aaaaaaaaaaaaaaaaaaif (tab[i].actif>1)
aaaaaaaaaaaaaaaaaaaaatab[i].actif--;
aaaaaaaaaaaaaaaaaa// quand actif==1 diminution du rayon jusque disparition du cercle
aaaaaaaaaaaaaaaaaaelse if (tab[i].r){
aaaaaaaaaaaaaaaaaaaaatab[i].r--;
aaaaaaaaaaaaaaaaaaaaaif (tab[i].r==0)
aaaaaaaaaaaaaaaaaaaaaaaatab[i].actif=0;
aaaaaaaaaaaaaaaaaa}
aaaaaaaaaaaaaaaaaa// si actif affiche éclat nouvelle position et taille
(3)aaaaaaaaaaaaaaaif (tab[i].actif){
aaaaaaaaaaaaaaaaaaaaax=(tab[i].x+=tab[i].pasx);
aaaaaaaaaaaaaaaaaaaaay=(tab[i].y+=tab[i].pasy);
aaaaaaaaaaaaaaaaaaaaar=tab[i].r;
aaaaaaaaaaaaaaaaaaaaarectfill(bmp,x-r,y-r,x+r,y+r,COLOR_CLAIR);
aaaaaaaaaaaaaaaaaa}
aaaaaaaaaaaaaa}
aaaaaaaaaa}
aaaaaa}

(1) La fonction ne renvoie rien et a comme paramètres l’adresse d’une bitmap et un pointeur t_boule*, lors de l’appel les arguments seront le double buffer et un tableau d’éclats.

(2) Chaque position du tableau est visitée. La variable du champ « actif » code et l’activité de l’éclat et le nombre d’affichages de l’éclat avant qu’il ne commence à diminuer de taille et disparaitre. Ainsi lorsqu’une position est active et supérieure à 1 la variable « actif » est tout d’abord décrémentée de 1 avant l’affichage en (3). Lorsque la variable du champ « actif » est égale à 1, c’est la diminution de la taille de l’éclat qui commence avec la décrémentation à chaque passage du rayon « r » et lorsque le rayon est égale à 0 l’éclat est rendu inactif.

(3) L’affichage de l’éclat a lieu s’il est toujours actif. A ce moment également la position de l’éclat est modifiée en fonction de son pas d’avancement. Le dessin est ici un rectangle de couleur claire. La couleur est prédéfinie avec la macro COLOR_CLAIR.


Mise en œuvre : compléter librairie et boucle d’événements

Librairie

Il y a juste à ajouter les deux macros vues précédement :

aaaa#define TAB_EXPLOSION 15
aaaa#define COLOR_CLAIR makecol(255,255,169)

Ainsi que les déclarations de fonctions :

aaaa// FONCTIONS PART 4
aaaa// 8. EXPLOSIONS
aaaavoid init_explosion (t_boule *tab1,t_boule *tab2);
aaaavoid arme_explosion (t_tank *t,t_boule *tab);
aaaavoid cntl_explosion (BITMAP *bmp,t_boule *tab);

Boucle d’évènements

Les deux tableaux pour les éclats sont déclarés dans le main() :

aaaat_boule tab1[TAB_EXPLOSION];
aaaat_boule tab2[TAB_EXPLOSION];

et les lignes en gras sont ajoutées dans la boucle d’événements :

aaaawhile (!done){
aaaaaaaablit(deco,buf, 0,0,0,0,deco->w, deco->h);
aaaaaaaapercute(buf,&t1);
aaaaaaaapercute(buf,&t2);
aaaaaaaabouge(&t1);
aaaaaaaabouge(&t2);
aaaaaaaadessine_tank(buf,&t1);
aaaaaaaadessine_tank(buf,&t2);

aaaaaaaaif (cntl_tir(buf,&t1,&t2))
aaaaaaaaaaaaarme_explosion(&t1,tab1);
aaaaaaaaif (cntl_tir(buf,&t2,&t1))
aaaaaaaaaaaaarme_explosion(&t2,tab2);
aaaaaaaacntl_explosion(buf,tab1);
aaaaaaaacntl_explosion(buf,tab2);

aaaaaaaaif (keypressed())
aaaaaaaaaaaadone=entree_clavier(buf,&t1,&t2);
aaaaaaaablit(buf,screen,0,0,2,14,buf->w,buf->h);
aaaaaaaarest(30);
aaaa}