2. Mise en forme et initialisation
L’idée
ici est d’appuyer la construction du tétris sur des matrices.
Une matrice 2D pour la zone de jeu et un ensemble de quatre matrices 2D
pour chaque forme. L’algorithme sera essentiellement de faire coïncider
les mouvements des formes (rotations, déplacement latéraux,
descente) avec le plan du jeu. C'est-à-dire contrôler que
la forme reste dans le plan et qu’aucune autre forme précédente
n’entrave son mouvement.
Plan 2D du
jeu
Le plan du jeu est définie en globale par une matrice de TY hauteur
sur TX largeur :
#define TY 10
#define TX 20
char PLAN[TY][TX];
Codage des
formes
Chaque pièce peut-être codée avec une matrice 2D de
4 lignes sur 4 colonnes mais du fait des rotations chaque pièce
doit pouvoir apparaître sous quatre angles différents. Si
l’on prend par exemple la forme barre « L envers » ça
donne :
Il y a moyen de coder
chaque pièce dans ses quatre positions par une matrice à
3 dimensions :

Dans le jeu classique il y a sept formes
différentes et nous aurons autant de tableaux 3D dans le jeu nommés
: la barre IF, le L à l’envers JF, le L à l’endroit
LF, le carré OF, le T TF, la marche escalier SF, l’autre
marche ZF.
Ils
sont tous initialisés en dur sur un fichier C à part, par
exemple la barre donne :
char IF[4][4][4]={
aaaaaaaaaaaaaaaaaaa{
aaaaaaaaaaaaaaaaaaaaaa{0, 0, 6, 0},
aaaaaaaaaaaaaaaaaaaaaa{0, 0, 6, 0},
aaaaaaaaaaaaaaaaaaaaaa{0, 0, 6, 0},
aaaaaaaaaaaaaaaaaaaaaa{0, 0, 6, 0}
aaaaaaaaaaaaaaaaaaa},
aaaaaaaaaaaaaaaaaaa{
aaaaaaaaaaaaaaaaaaaaaa{0, 0, 0, 0},
aaaaaaaaaaaaaaaaaaaaaa{0, 0, 0, 0},
aaaaaaaaaaaaaaaaaaaaaa{6, 6, 6, 6},
aaaaaaaaaaaaaaaaaaaaaa{0, 0, 0, 0}
aaaaaaaaaaaaaaaaaaa},
aaaaaaaaaaaaaaaaaaa{
aaaaaaaaaaaaaaaaaaaaaa{0, 0, 6, 0},
aaaaaaaaaaaaaaaaaaaaaa{0, 0, 6, 0},
aaaaaaaaaaaaaaaaaaaaaa{0, 0, 6, 0},
aaaaaaaaaaaaaaaaaaaaaa{0, 0, 6, 0}
aaaaaaaaaaaaaaaaaaa},
aaaaaaaaaaaaaaaaaaa{
aaaaaaaaaaaaaaaaaaaaaa{0, 0, 0, 0},
aaaaaaaaaaaaaaaaaaaaaa{0, 0, 0, 0},
aaaaaaaaaaaaaaaaaaaaaa{6, 6, 6, 6},
aaaaaaaaaaaaaaaaaaaaaa{0, 0, 0, 0}
aaaaaaaaaaaaaaaaaaa}
aaaaaaaaaaaaaaa};
Et
ils font l’objet ensuite d’une déclaration «
extern » dans le fichier C avec l’algorithme qui les utilise
:
extern char
IF[4][4][4];
extern char JF[4][4][4];
extern char LF[4][4][4];
extern char OF[4][4][4];
extern char TF[4][4][4];
extern char SF[4][4][4];
extern char ZF[4][4][4];
La forme
dans le plan
Trois variables globales vont permettre
de contrôler le mouvement de chaque forme :
int
XP ;aaaaaaaaapour
la position horizontale et
int YP ;aaaaaaaaapour la position verticale
à partir du coin haut gauche dans le plan
aaaaaaaaaaaaaaaAu départ XP centre
à l’horizontal la forme dans l’espace du jeu et YP
aaaaaaaaaaaaaaaplace la forme en haut de
l’espace de jeu :
aaaaaaaaaaaaaaaXP=3 ;
aaaaaaaaaaaaaaaYP=0 ;
int
FCOUR ;aaaaapour la position de rotation
(les indices 0,1,2,3 de la première dimension
aaaaaaaaaaaaaaadu tableau 3D FORME
aaaaaaaaaaaaaaaAu départ FCOUR est
initialisé sur la première position de la forme :
aaaaaaaaaaaaaaaFCOUR=0 ;
D’autre
part une seule forme est utilisée à la fois dans le plan,
c’est la forme « courante » et la forme « suivante
» est affichée sur le côté du plan pendant le
mouvement de la forme courante. Il est donc nécessaire de pouvoir
manipuler les tableaux à l’aide de deux variables de type
pointeur sur tableau 2D, un pointeur pour la forme courante et un pointeur
pour la forme suivante.
Le
type pointeur de tableau 2D est définit comme suit :
aaaaatypedef
char (*forme)[ 4 ][ 4 ] ;
et
nous avons en globale les deux pointeurs :
aaaaaforme
courante ;
aaaaaforme
suivante ;
Au
début de la partie ces pointeurs prennent aléatoirement
chacun l’adresse d’une forme avec la fonction suivante :
aaaaaforme
init_piece()
aaaaa{
aaaaaaaaaaswitch(rand()%7){
aaaaaaaaaaaaacase
0 : return SF;
aaaaaaaaaaaaacase
1 : return ZF;
aaaaaaaaaaaaacase
2 : return LF;
aaaaaaaaaaaaacase
3 : return JF;
aaaaaaaaaaaaacase
4 : return TF;
aaaaaaaaaaaaacase
5 : return IF;
aaaaaaaaaaaaacase
6 : return OF;
aaaaaaaaaa}
aaaaaaaaaareturn
NULL;
aaaaa}
L’appel
se fait de la façon suivante avant la boucle d’événements
du jeu :
aaaaa(…)
aaaaacourante
= init_piece();
aaaaasuivante
= init_piece();
aaaaa(…)
Lorsque
la forme courante a terminé sa course et se trouve bloquée
en bas, le changement de forme courante est opéré de la
façon suivante :
aaaaavoid
init_piece_suivante()
aaaaa{
aaaaaaaaaacourante=suivante;
aaaaaaaaaasuivante=init_piece();
aaaaaaaaaaXP=3;
aaaaaaaaaaYP=0;
aaaaaaaaaaFCOUR=0;
aaaaa}
«
courante » prend la valeur de « suivante », suivante
est réinitialisée avec la fonction init_piece() et les valeurs
de départ sont réaffectées aux variables globales
XP, YP et FCOUR.
3. Processus générateur
Dépôt
des formes dans la zone de jeu
Le
principe du jeu est que les formes arrivent en bas de la zone de jeu et
y soient stockées selon l’adresse du joueur plus ou moins
bien encastrées les unes dans les autres. Le stockage des formes
dans le plan consiste en une simple recopie de la matrice forme à
partir de la position finale dans la zone de jeu donnée par les
variables globales (XP, YP).
Plus précisément ne seront copiées que les valeurs
des cases positives et différentes de zéro. C’est
le rôle de la fonction fixer_piece() :
aaaaavoid
fixer_piece(forme F)
aaaaa{
aaaaaint
x,y;
(1)aaaaafor
(y=0; y<4; y++)
aaaaaaaaaaafor
(x=0; x<4; x++)
(2)aaaaaaaaaaaif
( F[FCOUR][y][x] )
(3)aaaaaaaaaaaaaaPLAN[YP+y][XP+x]=F[FCOUR][y][x];
aaaaa}
aaaaa(1)
Toutes les positions de la matrice forme « F » sont passées
en revue,
aaaaa(2) Si l’on est sur une position
(x,y) différente de zéro
aaaaa(3) La valeur de cette position est
recopiée à la position (XP+x, YP+y) de la matrice PLAN.
Les mouvements
Trois
types de mouvements possibles dans le jeu : latéraux, rotation
de la pièce et descente.
La
situation est la suivante :
aaaaa
(XP,YP)
donne la position de la matrice « forme » (en gris) dans le
plan. (x, y) donnent la taille de la matrice forme.
Pour bouger la matrice forme il faut modifier XP et YP et vérifier
que les toutes les positions de la matrice forme (XP+x, YP+y ) :
aaaa1)
restent dans la zone de jeu
aaaa2)
ne se superposent pas avec des formes déjà déposées
dans la zone de jeu
Mouvements
latéraux
Il
suffit de modifier la variable XP :
La touche clavier flèche gauche diminue XP de 1, la touche flèche
droite augmente XP de 1
Le test vérifie d’abord si la forme reste dans la surface
de jeu, c'est-à-dire la matrice PLAN et ensuite vérifie
que la forme ne se superpose pas à une autre forme déjà
déposée dans la matrice PLAN. Ce sont les deux fonctions
move_gauche() et move_droite() :
aaaaavoid
move_gauche(forme F)
aaaaa{
aaaaaint
x,y;
(1)aaaaafor
(y=0; y<4; y++)
aaaaaaaaaaafor
(x=0; x<4; x++)
(2)aaaaaaaaaaaif
( F[FCOUR][y][x] && ( (XP-1+x<0) || (PLAN[YP+y][XP-1+x]) )
)
aaaaaaaaaaaaaaaaareturn;
(3)aaaaaXP--;
aaaaa}
(1)
Toutes les positions de la matrice forme sont passées en revue
(2) Si on est sur une position différente de zéro, le mouvement
n’aura pas lieu si :
aaaaa- si cette position transposée
dans le plan du jeu avec ajout de la variable XP diminuée de 1
(cause mouvement gauche) est aaaaaastrictement
inférieure à 0
aaaaa- ou si la position correspondante dans
la matrice est déjà occupée
aaaDans ces deux cas sortie de la fonction
sans modification de la variable XP.
(3) Si la forme reste dans la zone de jeux et que les positions correspondantes
sont
aaalibres XP est diminuée de 1
Pour
la fonction move_droite le principe est identique avec une modification
du test qui porte maintenant sur le bord droit (TX) et non plus gauche
( avec 0 )
aaaaavoid
move_droite(forme F)
aaaaa{
aaaaaint
x,y;
(1)aaaaaafor
(y=0; y<4; y++)
aaaaaaaaaaaafor
(x=0; x<4; x++)
(2)aaaaaaaaaaaaif
( F[FCOUR][y][x] && (
(XP+1+x>=TX) || (PLAN[YP+y][XP+1+x]) ) )
aaaaaaaaaaaaaaaaaareturn;
(3)aaaaaaXP++;
aaaaa}
(1)
Toutes les positions de la matrice forme sont passées en revue
(2) Si on est sur une position différente de zéro, le mouvement
n’aura pas lieu si :
aaaaa- si cette position transposée
dans le plan du jeu avec ajout de la variable XP augmentée de 1
(cause mouvement droite) est aaaaaastrictement
inférieure ou égal à TX
aaaaa- ou si la position correspondante dans
la matrice est déjà occupée
aaaDans ces deux cas sortie de la fonction
sans modification de la variable XP.
(3) Si la forme reste dans la zone de jeux et que les positions correspondantes
sont
aaalibres XP est augmentée de 1
Rotation
de la pièce
La
rotation de la pièce laisse inchangée la position XP,YP
en revanche la représentation de la forme change. Cette représentation
est donnée par FCOUR qui indique le numéro d’ordre
de la matrice forme courante. La rotation à gauche diminue de 1
FCOUR afin de venir à la matrice précédente. Ce mouvement
est obtenue par pression de la touche alt. La rotation à droite
augmente de 1 FCOUR afin de passer à la matrice suivante.
La fonction rotation() prend deux arguments, la forme concernée
et le sens (gauche ou droite, -1 ou 1) de la rotation :
aaaaavoid
rotation ( forme F, int g_d)
aaaaa{
aaaaaint
suiv, x, y;
(1)aaaaasuiv
= ((FCOUR+g_d)+4)%4;
(2)aaaaafor
(y=0; y<4; y++)
aaaaaaaaaafor
(x=0; x<4; x++)
aaaaaaaaaaaaaif
( (F[suiv][y][x]) && a(
XP+x <0 || XP+x >=TX || aYP+y
>=TY || PLAN[YP+y][XP+x] ) )
aaaaaaaaaaaaaaaareturn;
(3)aaaaaFCOUR=suiv;
aaaaa}
(1)
La rotation ne pourra avoir lieu que si la pièce dans sa nouvelle
position reste dans la zone de jeu et ne se superpose pas avec une forme
déjà fixée dans la zone de jeu. Pour ce faire l’éventuelle
position suivante, si la rotation a lieu, est d’ abord récupérée
dans une variable temporaire « suiv » qui va permettre de
faire tous les tests nécessaires. La formule ((FCOUR+d_G)+4)%4
permet de rester dans l’interval 0-3 quelque soit la valeur -1 ou
1 de la variable « g_d ».
(2)
Ensuite toutes les positions de la matrice forme suivante sont examinées
:
aaaaa- Si la position est position différente
de zéro,
aaaaa- Le mouvement n’aura pas lieu
si cette position transposée à la matrice par ajout de XP
est soit inférieure à 0 soit supérieure ou aaaaaaégal
à TX.
aaaaa
- Le mouvement n’auraapas lieu non
plus si la position dans la zone de jeu, c’est dire dans la matrice
PLAN, n’est pas libre.
aaaaaDans tous ces cas la sortie de la fonction
est provoquée sans modification de la variable FCOUR.
(3)
Sinon la variable FCOUR prend la valeur de la variable « suiv ».
Descente
Horizontalement
il n’y que la descente de possible et elle s’effectue de toute
façon automatiquement à un rythme qui dépend du niveau
atteint dans la partie (le code correspondant est expliqué plus
bas avec le main() et la boucle d’événements). Inexorablement
la pièce descend et il faut absolument lui trouver une place dans
l’empilement en bas.
La fonction descendre() contrôle le processus de descente. Elle
renvoie la valeur 0 si la forme, au coup d’après, va sortir
du plan ou se superposer à une autre, c'est-à-dire lorsqu’elle
doit s’immobiliser et qu’une autre fore soit envoyée
dans le jeu. Elle renvoie la valeur -1 si en plus de cette immobilité
la variable YP est égale à 1 c'est-à-dire s’il
n’y a plus possibilité de faire descendre des nouvelles formes
et que la partie est finie et perdue.
aaaaaint
descendre( forme F)
aaaaa{
aaaaaint
x,y,res=1;
(1)aaaaaaYP++;
(2)aaaaaafor
(y=0; y<4; y++)
aaaaaaaaaaafor
(x=0; x<4; x++){
aaaaaaaaaaaaaaif
( F[FCOUR][y][x] && a(
(YP+1+y>=TY)||(PLAN[YP+1+y][XP+x]) ) ){
aaaaaaaaaaaaaaaaaif
(YP==1)
aaaaaaaaaaaaaaaaaaaares=-1;
aaaaaaaaaaaaaaaaaelse
aaaaaaaaaaaaaaaaaaaares=0;
aaaaaaaaaaaaaaaaa//
fin boucle éventuellement
aaaaaaaaaaaaaaaaay=4;
aaaaaaaaaaaaaaaaax=4;
aaaaaaaaaaaaaa}
aaaaaaaaa}
(3)aaaaaareturn
res;
aaaaa}
(1)
La variable YP est incrémentée dés le début.
(2) Toutes les positions correspondant à la forme courante sont
passées en revue. Pour montrer que la forme ne peut plus descendre
les deux conditions suivantes doivent être réunies :
aaaaa- on est sur une position différente
de 0 dans la matrice forme
aaaaa- et soit la position verticale transposée
à l’espace de jeu sort de l’espace de jeu, c'est-à-dire
si la variable YP+1 dépasse la taille aaaaaaverticale
TY de la matrice PLAN soit cette position se superpose à une valeur
différente de 0 dans la matrice PLAN (c'est-à-dire si aaaaaaune
forme y a déjà été déposée auparavant).
S’il apparaît que la forme ne peut plus descendre il faut
en plus vérifier que la partie n’est pas finie à savoir
que YP est différent de 1. En effet l’initialisation de YP
est 0 pour une forme au départ et comme YP est premièrement
incrémentée de 1 au début de la fonction, au minimum
YP est égal à 1. Si donc YP est en plus égal à
1 la partie est finie et le code de retour -1 est stocké dans la
variable res. Si YP est différent de 1 la partie n’est pas
finie, le code de retour est alors 0, la pièce courante devient
la pièce suivante, la pièce suivante est réinitialisée.
(3) Retour du résultat.
Contrôle
des mouvements au clavier
Nous
avons vu l’implémentation des différents mouvements
possibles dans le jeu, ces mouvements sont commandés avec le clavier
contrôle
du clavier
Le clavier est abordé dans l’environnement allegro
avec les scancodes, ce qui est très rapide et simple d’utilisation
:
aaaaaif (key[KEY_UP])
aaaaaaaa// actions
Cependant
il est nécessaire ici de contrôler la répétition
des touches d’où l’ajout de la fonction suivante :
(1)aavoid
cntl_clavier(int k, int *m){
(2)aaaaaif
( ! key[k])
aaaaaaaaaa*m=0;
// si non appuyée m à 0
aaaaaaaelse
aaaaaaaaaa(*m)++;
// si appuyée m augmenté de 1
aaaaa}
(1)
La fonction cntl_clavier() prend deux arguments. « k » va
correspondre à une valeur de scancode et désigner une touche
du clavier. « m » est une variable entière passée
par référence dont on pourra commettre la valeur dans le
contexte d’appel : si m vaut 0 c’est que la touche n’est
pas appuyée et sinon c’est que la touche est déjà
appuyée. Dans le contexte d’appel cette variable sera déclarée
de type static.
(2)
Pour savoir la touche « k » est appuyée ou non il suffit
de mettre « m » à 0 lorsqu’elle n’est pas
appuyée et sinon d’incrémenter « m » (ce
qui va permettre éventuellement de contrôler le nombre de
tours pendant lesquels la touche reste appuyée).
Dans
la fonction appelante nous avons une utilisation de la fonction cntl_clavier()
de la façon suivante, par exemple pour le contrôle de la
touche flèche droite :
aaaaastatic
int cntl=0 ;
aaaaaaaacntl_clavier( KEY_RIGHT, &cntl)
;
aaaaaaaaif (cntl==1)
aaaaaaaaaaamove_droite(courante);
Dans
cet exemple move_droite() n’est appelée que si cntl vaut
1 et n’est pas appelé lorsque la touche n’est pas pressée
si cntl vaut 0 ou en cas de répétition si cntl vaut plus
que 1.
Autre possibilité de contrôle, qui accepte la répétions
à partir d’une pression longue :
aaaaaaaaif (cntl==1 || cntl>15)
aaaaaaaaaaamove_droite(courante);
contrôle
des mouvements lattéraux, rotation et descente
Les contrôles des mouvements latéraux, de rotation
et de descente sont regroupés dans la fonction move() suivante
:
aaaaavoid
move()
aaaaa{
(1)aaa#define
GAUCHE -1
aaaaa#define
DROITE 1
aaaaastatic
int mg=0,md=0,rg=0,rd=0,dcs=0;
(2)aaaaaacntl_clavier(KEY_RIGHT,&md);
aaaaaaaaif(
md==1 || md>15)
aaaaaaaaaaamove_droite(courante);
aaaaaaaacntl_clavier(KEY_LEFT,
&mg);
aaaaaaaaif(mg==1
|| mg >15)
aaaaaaaaaaamove_gauche(courante);
(3)aaaaacntl_clavier(KEY_LCONTROL,
&rg);
aaaaaaaaif
(rg==1)
aaaaaaaaaaarotation(courante,GAUCHE);a
aaaaaaaacntl_clavier(KEY_ALT,
&rd);
aaaaaaaaif
(rd==1)
aaaaaaaaaaarotation(courante,DROITE);
(4)aaaaacntl_clavier(KEY_DOWN,
&dcs);
aaaaaaaaif
(dcs)
aaaaaaaaaaat_piece=200;
aaaaa}
(1)
La fonction move() ne prend pas d’argument et ne renvoie rien. Une
suite de variables entières static sont déclarée
et initialisée lors du premier appel de la fonction à la
valeur 0. Deux noms symboliques sont définis GAUCHE et DROITE pour
les valeurs de -1 et 1.
(2)
Contrôles des mouvements latéraux avec les flèches
droites et gauche
(3)
Contrôle des mouvement de rotation avec les flèches Alt et
Ctrl de gauche
(4)
Contrôle de la descente avec la flèche bas. Indépendamment
du contrôle clavier la descente s’effectue de toute façon
automatiquement et plus ou moins vite selon l’avancement de la partie.
Les rythmes correspondent aux différents niveaux de la partie.
Chaque rythme est une valeur entière par exemple 70 (premier niveau).
Dans le main les niveaux sont stockés dans un tableau d’entiers
:
aaaaaaaaint niveau[25]= {70, 65, 60, 55,
50, 45, 40, 35, 30, 26, 22, 19, 16, 14, 12, 10, 8, 7, 6, 5, 4, 3, 2, 1,
0};
et le niveau courant est géré via une variable « speed
» qui indique à quel indice on est dans le tableau.
Une variable globale « t_piece » est incrémentée
régulièrement via un timer et dans la boucle d’événements
du main() la descente est effectuée à chaque fois que la
variable t_piece est supérieure à la valeur du niveau courant
de la façon suivante :
aaaaaaaaif (t_piece>niveau[speed]){
aaaaaaaaaaat_piece=0 ;
aaaaaaaaaaa// action descente
aaaaaaaa}
Ainsi
donc mettre la variable globale t_piece à 200 si la touche flèche
bas est appuyée équivaut à forcer la vitesse de descente
indépendamment du rythme courant.
«
game logic », suppression des lignes horizontales pleines
La
« logique » du jeu correspond à la gestion de la zone
de jeux, la matrice PLAN. Quand une pièce ne peut plus descendre
elle est « fixée » dans la zone de jeu c’est
dire qu’elle est recopiée dans la matrice PLAN à sa
position finale avec la fonction fixer_piece() vue précédemment.
La pièce courante prend alors la valeur de la pièce suivante
et une nouvelle future pièce suivante est initialisée avec
la fonction init_piece(). La transition et réalisée avec
un appel unique à la fonction init_piece_suivante() également
vu précédemment. Il reste la question de la suppression
des lignes horizontales pleines, chaque ligne horizontale pleine est en
effet une victoire du joueur et leurs suppressions permet de durer contre
l’empilement inexorable...
Compter les lignes finies
A chaque fin de tour, lorsqu’une pièce est immobilisée
et qu’une nouvelle va être introduite dans la partie, il faut
vérifier s’il n’y a pas une ou plusieurs lignes horizontales
qui seraient constituées. Il faut donc parcourir chaque ligne de
la matrice PLAN et vérifier si elle est pleine, si oui conserver
le numéro de ligne afin de pouvoir la supprimer ensuite.
Parcourir chaque ligne est assez simple, la ligne est pleine si toutes
ses positions dans la matrice contiennent une valeur différente
de zéro. L’idée pour le stockage consiste à
avoir un tableau d’entier de la taille du nombre de lignes TY initialisé
à 0, à chaque fois qu’une ligne pleine est trouvée,
la valeur 1 est affectée à sa position dans le tableau,
ensuite il suffit de parcourir ce tableau pour savoir quelles sont les
lignes pleines à retirer de la zone de je, la matrice PLAN. La
situation est la suivante :
aaaaa
Répertorier les lignes pleines peut s’effectuer avec la fonction
suivante :
(1)aaaint
compter_lignes_finies(int *lgns)
aaaaa{
aaaaaint
y,x,cmpt=0;
(2)aaaaaamemset(lgns,0,sizeof(int)*TY);
(3)aaaaaafor
(y=0; y<TY; y++){
aaaaaaaaaaafor
(x=0; x<TX; x++)
aaaaaaaaaaaaaaif(!
PLAN[y][x])
aaaaaaaaaaaaaaaaabreak;
(4)aaaaaaaaaif
(x==TX){
aaaaaaaaaaaaaalgns[y]=1;
aaaaaaaaaaaaaacmpt++;
aaaaaaaaaaa}
aaaaaaaa}
(5)aaaaareturn
cmpt;
aaaaa}
(1)
La fonction compter_ligne_finie() est appelée dans la boucle d’événements,
dans le main(). Elle prend un tableau en argument et il est déclaré
dans le main. La fonction retourne une valeur entière, le nombre
de lignes et la suppression des lignes pourra n’être appelée
ensuite que s’il y a des lignes à supprimer.
(2)
Initialisation à 0 du tableau avec la fonction memset().
(3)
Le parcours de la matrice PLAN pour chaque ligne y toutes les positions
horizontales x sont évaluées mais dés qu’une
valeur de 0 est trouvé sur une ligne le passage à la ligne
suivante est forcé avec l’instruction break ;
(4)
A la fin du parcours de chaque ligne si x à la valeur de TX c’est
que la ligne « y » est pleine dans ce cas la valeur 1 est
affectée à l’indice y dans le tableau « lgns
» et le compteur de lignes est augmenté de 1.
(5)
Le nombre de lignes pleines trouvées est retourné.
Effacer
les lignes finies
Pour
faire disparaître les lignes finies l’idée est simplement
de ne recopier que les autres dans l’ordre où elles se présentent
selon le principe suivant :
aaaaa
Dans
une matrice miroir de la zone de jeu « PLAN », les lignes
non finies uniquement sont recopiées les unes à la suite
des autres en partant du bas. La fonction de recopie des lignes non finies
est la suivante :
(1)aaavoid
effacer_lignes_finies(int *lgns)
aaaaa{
aaaaachar
plan[TY][TX];
aaaaaint
y,k;
(2)aaaaaamemset(plan,0,sizeof(char)*TY*TX);
(3)aaaaaafor
(k=y=TY-1; y>0; y--)
aaaaaaaaaaaif(!lgns[y]){
aaaaaaaaaaaaaamemcpy(&plan[k],&PLAN[y],sizeof(char)*TX);
aaaaaaaaaaaaaak--;
aaaaaaaaaaa}
(4)aaaaaamemcpy(PLAN,plan,sizeof(char)*TY*TX);
aaaaaa}
(1)
La fonction ne renvoie rien et a en paramètre un pointeur d’entier.
Ce pointeur est destiné à recevoir un le tableau utilisé
dans la fonction précédente compter_ligne_finie(), un tableau
d’entiers de TY taille. Local à la fonction une matrice miroir
de la matrice PLAN qui représente la jeu de jeu.
(2)
La matrice locale destinée à recevoir les résultats
de la copie des lignes non finies est d’abord initialisée
à 0.
(3)
Dans la boucle for deux variables k et y sont initialisées sur
l’indice de la dernière ligne TY-1, y va suivre les lignes
de la matrice PLAN et k va progresser en fonction des lignes non finies
trouvées. Le tableau « lgns » contient des valeurs
de 0 pour une ligne non finie et 1 pour une ligne finie. Chaque ligne
non finie est recopiée avec la fonction memcpy() dans la matrice
locale « plan ».
(4)
A l’issu la matrice résultante « plan » est recopiée
dans la matrice « PLAN » zone de jeu.
Affichage
Les
aspects graphiques consiste en huit bitmaps : sept carreaux de couleurs
avec un léger relief qui servent à constituer les différentes
pièces et le dessin du pourtour de la zone de jeu. Toutes ces bitmaps
sont regroupées dans un fichier « * .dat » créé
avec l’utilitaire Grabber de la librairie Allegro.
L’affichage
se fait dans un double buffer, une BITMAP* déclarée en globale
et nommée PAGE :
aaaaaaaaBITMAP*PAGE
;
Tout
ce qui concerne l’affichage est regroupé finalement dans
une seule procédure : cntl_affichage() et cette procédure
est simplement appelée une fois au début dans la boucle
d’événements. Cette procédure regroupe et coordonne
les appels de trois sous-fonctions :
aaaaaaaa- dessine_plan() est chargée
d’afficher la zone de jeu c'est-à-dire le contenu de la matrice
PLAN
aaaaaaaa- dessine_forme_dans_plan() affiche
la forme courante à sa position courante
aaaaaaaa- dessine_forme_hors_plan() affiche
la forme suivante en attente sur le bord de la zone de jeu.
Gestion
des ressources bitmap
Créer
un fichier dat avec le Grabber renvoie à la documentation du Grabber.
Dans le programme il apparaît via la structure de donnée
suivante :
aaaaaaaatypedef struct DATAFILE
aaaaaaaa{
aaaaaaaaaaavoid *dat; - pointeur sur la donnée
actuelle
aaaaaaaaaaaint type; - le type de la donnée
aaaaaaaaaaalong size; - la taille de la donnée
en octets
aaaaaaaaaaavoid *prop; - une des propriétés
de l’objet
aaaaaaaa} DATAFILE;
A
titre indicatif, le champ type peut contenir une des valeurs suivantes
(voir documentation allegro à datafile routines ) :
aaaaaaaaDAT_FILE - dat pointe vers a nested
datafile
aaaaaaaaDAT_DATA - dat pointe vers un bloc
de données binaires
aaaaaaaaDAT_FONT - dat pointe vers un objet
fonte
aaaaaaaaDAT_SAMPLE - dat pointe vers une
structure SAMPLE
aaaaaaaaDAT_MIDI - dat pointe vers un fichier
MIDI
aaaaaaaaDAT_PATCH - dat pointe vers un «
GUS patch file »
aaaaaaaaDAT_FLI - dat pointe vers une animation
FLI/FLC
aaaaaaaaDAT_BITMAP - dat pointe vers une
structure BITMAP
aaaaaaaaDAT_RLE_SPRITE - dat pointe vers
une structure RLE_SPRITE
aaaaaaaaDAT_C_SPRITE - dat pointe vers un
« linear compiled sprite »
aaaaaaaaDAT_XC_SPRITE - dat pointe vers un
« mode-X compiled sprite »
aaaaaaaaDAT_PALETTE - dat pointe vers an
tableau de 256 RGB structures
aaaaaaaaDAT_END - flag spécial qui
marque la fin de la liste des données
En
ce qui concerne notre programme, une fois ce fichier créé
il est utilisé dans le programme via un pointeur du type DATAFILE
*, déclaré en globale :
aaaaaaaaDATAFILE *GRPH;
L’initialisation
du pointeur se fait avec la fonction load_datafile() de l’environnement
Allegro de la façon suivante :
aaaaaaaaGRPH=load_datafile(".//graphic.dat");
aaaaaaaaif(!GRPH ){
aaaaaaaaaaaallegro_message("erreur GRPH");
aaaaaaaaaaaallegro_exit();
aaaaaaaaaaaexit(1);
aaaaaaaa}
Cette initialisation a lieu dans la fonction init_env() présentée
plus bas dans la section « mise en œuvre dans le main et boucle
d’événements. Dans
le programme, le fichier dat apparaît alors sous la forme d’un
tableau, chaque ressource correspond à un indice du tableau et
peut être récupérée de la façon suivante
:
aaaaaaaaBITMAP
* bmp ;
aaaaaaaaaaabmp=(BITMAP*)GRPH[
1 ].dat
Attention
le cast est nécessaire car le champ dat est un void*. Dans le programme
les carreaux de couleurs correspondent aux indices 1 à 7 compris,
valeurs utilisées pour la définition des formes (voir section
« codage des formes » plus haut). L’indice 8 correspond
à l’image de la zone de jeu.
Commande de l’affichage et du rafraîchissement dans
la boucle des événements
Avant de chacune des fonctions qui composent l’affichage, voici
tout d’abord la fonction qui les regroupe et les articule. Cette
fonction du contrôle global de l’affichage cntl_affichage()
est appelée une fois au début de la boucle d’événement
:
aaaaavoid
cntl_affichage()
aaaaa{
(1)aaaaaaclear(PAGE);
(2)aaaaaadraw_sprite(PAGE,(BITMAP*)GRPH[8].dat,
210,30);
(3)aaaaaadessine_plan();
(4)aaaaaadessine_forme_dans_plan(courante);
(5)aaaaaadessine_forme_hors_plan(suivante,500,100);
(6)aaaaaablit(PAGE,screen,0,0,0,0,640,480);
aaaaa}
(1)
Tout d’abord le double buffer, c'est-à-dire la bitmap PAGE
déclarée en globale est effacée. C’est la bitmap
qui sert à assembler tous les éléments de l’image
avant affichage à l’écran.
(2) Ensuite comme premier élément graphique de l’assemblage
vient le dessin du cadre de la zone de jeu. Ce dessin est une bitmap à
l’indice 8 du tableau GRPH[ ] des ressources du jeu.
(3) La fonction dessine_plan() s’occupe de dessiner dans PAGE la
matrice PLAN[ ][ ]
(4) La fonction dessine_forme_dans_plan() prend en paramètre le
pointeur courante et dessine la forme en train de descendre à sa
position courante dans la zone de jeu
(5) La fonction dessine_forme_hors_plan() concerne l’affichage de
la forme suivante déjà visible sur le bord de la zone de
jeu. Elle prend en paramètre le pointeur suivante et la position
à l’écran.
(6) L’affichage final de la bitmap PAGE complète à
l’écran avec la fonction allegro blit().
Le
plan et l’accumulation des formes
La
fonction dessine_plan() permet de transposer graphiquement l’état
de la matrice PLAN. Il s’agit simplement de parcourir la matrice
PLAN. Lorsqu’une position est différente de 0, à savoir
une valeur entre 1 et 7, afficher la bitmap à l’indice correspondant
dans le tableau GRPH[ ] :
aaaaavoid
dessine_plan()
aaaaa{
aaaaaBITMAP
*bmp;
aaaaaint
x,y,x2,y2,k;
(1)aaaaaafor
(y=0; y<TY; y++)
aaaaaaaaaaafor
(x=0; x<TX; x++)
aaaaaaaaaaaaaaif
(k=PLAN[y][x]){ // attention c'est bien une affectation
(2)aaaaaaaaaaaaaabmp=(BITMAP*)GRPH[k].dat;
aaaaaaaaaaaaaaaaax2=
MARGE_HOR + x*bmp->w;
aaaaaaaaaaaaaaaaay2=
MARGE_VER + y*bmp->h;
aaaaaaaaaaaaaaaaablit(bmp,PAGE,0,0,x2,y2,bmp->w,bmp->h);
aaaaaaaaaaaaaa}
aaaaa}
(1)
Chaque position de la matrice PLAN est évaluée avec une
double boucle for. La valeur de la position est récupérée
dans la variable
« k ». Si sa valeur est supérieure à 0 les instructions
suivantes sont exécutées :
(2) L’adresse de la bitmap correspondante, c'est-à-dire à
l’indice « k » dans le tableau GRPH[ ] de ressources
dat, est récupérée dans un BITMAP* local «
bmp » et sa position est calculée. MARGE_HOR et MARGE_VER
sont deux macros qui définissent un décalage horizontal
et un décalage vertical. Ces décalage sont respectivement
ajoutés à la position horizontal en x dans la matrice multipliée
par la longueur de la bitmap et à la position verticale en y multipliée
par la hauteur de la bitmap. Ensuite la bitmap « bmp » est
copiée à l’écran à la position résultante
(x2,y2) avec la fonction blit() d’allegro.
La forme courante à sa position dans la zone de jeu
Après la zone de jeu et le contenu de matrice PLAN, vient l’affichage
de la pièce courante qui descend régulièrement. La
position de la pièce courante est donnée par les variables
globales XP et YP, il n’y a qu’à parcourir la matrice
correspondant à la forme courante donnée par FCOUR et afficher
comme précédemment une bitmap du fichier ressource dans
le tableau GRPH[ ]. La fonction dessine_forme_dans_plan() prend un pointeur
de matrice du type forme en argument « forme F » et ainsi
:
aaaaavoid
dessine_forme_dans_plan(forme F)
aaaaa{
aaaaaBITMAP
*bmp;
aaaaaint
x,y,k,x2,y2;
(1)aaaaaafor
(y=0; y<4; y++)
aaaaaaaaaaafor
(x=0; x<4; x++)
aaaaaaaaaaaaaaif
( k = F[FCOUR][y][x]){// attention c'est bien une affectation
(2)aaaaaaaaaaaaaabmp=(BITMAP*)GRPH[k].dat;
aaaaaaaaaaaaaaaaax2
= (XP*bmp->w) + (x*bmp->w) + MARGE_HOR;
aaaaaaaaaaaaaaaaay2
= (YP*bmp->h) + (y*bmp->h) + MARGE_VER;
aaaaaaaaaaaaaaaaablit(bmp,
PAGE, 0,0,x2,y2,bmp->w,bmp->h);
aaaaaaaaaaaaaa}
aaaaa}
(1)
Chaque position de la matrice F[FCOUR][y][x] est évaluée
avec une double boucle for. La valeur de la position est récupérée
dans la variable « k ». Si sa valeur est supérieure
à 0 les instructions suivantes sont exécutées :
(2) L’adresse de la bitmap correspondante, c'est-à-dire à
l’indice « k » dans le tableau GRPH[ ] de ressources
dat, est récupérée dans un BITMAP* local «
bmp » et sa position est calculée. XP multiplié par
la longueur de la bitmap plus le décalage horizontal donne la position
horizontale. YP multiplié par la hauteur de la bitmap plus le décalage
vertical donne la position vertical. Ensuite recopie Ensuite la bitmap
« bmp » est copiée à l’écran à
la position résultante (x2,y2) avec la fonction blit() d’allegro.
La
prochaine forme en attente en dehors de la zone de jeu
L’affichage
de la forme en attente à l’extérieur de la zone de
jeu consiste à parcourir la matrice qui correspond à la
forme suivante dans sa première position (indice 0) et comme précédent
afficher les bitmaps qui la constituent mais cette fois à une position
fixe en dehors de la zone de jeu :
(1)aaavoid
dessine_forme_hors_plan(forme F, int xp, int yp)
aaaaa{
aaaaaBITMAP
*bmp;
aaaaaint
x,y,k;
(2)aaaaaafor
(y=0; y<4; y++)
aaaaaaaaaaafor
(x=0; x<4; x++)
aaaaaaaaaaaaaaif
( k = F[0][y][x]){aaaaa//
attention c'est bien une affectation
(3)aaaaaaaaaaaaaabmp=(BITMAP*)GRPH[k].dat;
aaaaaaaaaaaaaaaaablit(bmp,PAGE,0,0,xp+(x*bmp->w),yp+(y*bmp->h),bmp->w,bmp->h);
aaaaaaaaaaaaaa}
aaaaa}
(1)
La fonction dessine_forme_hors_plan() prend en argument un pointeur de
type forme et deux variables entières xp et yp qui vont correspondre
aux coordonnées écran de l’affichage de la forme.
(2) Comme dans les fonction précédentes, double boucle for
avec récupération de la valeur de la matrice à la
position (x,y) et teste sur cette valeur. Positive elle désigne
un indice dans le tableau GRPH des ressources.
(3) à l’indice k l’adresse de la bitmap correspondante
est récupérée dans le pointeur bmp afin de simplifier
l’écriture de la recopie écran avec la fonction blit()
à la position (xp, yp)
Mise
en place d’un timer, gestion du temps
Nous
avons déjà évoqué la variable « t_piece
» qui fait l’objet d’une incrémentation automatique
à intervals réguliers (voir « Contrôle des mouvements
latéraux, rotation et descente » plus haut). Une seconde
variable du même type est utilisée dans le jeu, c’est
la variable « t_jeu » qui va servir à stabiliser la
vitesse d’exécution du jeu indépendamment de la vitesse
du processeur. Le timer ou plus exactement la mise en place d’un
« interrupt handler » désigne le mécanisme qui
va permettre l’incrémentation en quasi parallèle de
ces variables. Ces variables vont servir de frein avec une construction
du type :
aaaaaaaawhile
(t_jeu>0){
aaaaaaaaaaat_jeu--
;
aaaaaaaaaaa//
instructions
aaaaaaaa}
Lorsque t_jeu a été incrémenté les instructions
sont exécutées et si elles sont exécutées
plus rapidement que l’incrémentation suivante le programme
attend la prochaine incrémentation. Ce mécanisme permet
de rythmer le déroulement du programme sans pour autant paralyser
le processeur dans une boucle d’attente.
Des variables de type « volatile »
L'utilisation d'un interrupt handler nécessite des variables "volatile",
nos deux variables sont déclarées en globale et du type
volatile int :
aaaaaaaavolatile
int t_piece=0 ;
aaaaaaaavolatile
int t_jeu=0 ;
Une fonction appelée à intervalle régulier
Leur incrémentation à intervals réguliers dans le
temps est produite à l’aide de la fonction Timer() ci-dessous.
Cette fonction doit être la plus simple possible et en aucun cas
faire appel à de gros calculs :
aaaaaaaavoid
Timer(void)
aaaaaaaa{
aaaaaaaaaaat_jeu++;
aaaaaaaaaaat_piece++;
aaaaaaaa}
aaaaaaaaEND_OF_FONCTION(Timer);
Son
écriture est close par une macro END_OF_FONCTION(Timer) (se reporter
à la documentation Allegro ainsi qu’à son code source
pour avoir des informations à ce sujet.)
Définition de l’intervalle des appels
Deux fonctions dans l’environnement Allegro permettent de définir
un intervalle de temps :
aaaaaaaaint
install_int(void (*proc)(), int speed)
aaaaaaaaint
install_int_ex(void (*proc)(), int speed)
Chacune
a deux arguments. Le premier est un pointeur de fonction qui va désigner
la fonction à appeler régulièrement par interruption
dans le temps. Le second est une valeur entière qui définit
la durée de l’intervalle de temps.
Pour la première l’intervalle de temps est donné uniquement
en milliseconde et pour la seconde fonction plusieurs options pour définir
le temps d’attente sont possibles, définies par les macros
suivantes :
aaaaaaaaSECS_TO_TIMER(secs)aaaaa
- donne le nombre de secondes entre chaque appel
aaaaaaaaMSEC_TO_TIMER(msec)aaaa
- donne le nombre de millisecondes entre chaque appel
aaaaaaaaBPS_TO_TIMER(bps)aaaaaaa
- donne le nombre d’appels par seconde
aaaaaaaaBPM_TO_TIMER(bpm)aaaaaa
- donne le nombre d’appels par minute
Dans
le programme nous avons choisi un appel tous les 1/60 de seconde définit
avec la fonction install_in_ex() de la façon suivante :
aaaaaaaainstall_int_ex(Timer,
BPS_TO_TIMER(60));
Initialisation au début du programme
Pour mettre en œuvre le timer, nous avons ensuite les lignes suivantes
au moment des initialisations du début du main() dans la fonction
init_env() déjà évoquée qui regroupe toutes
les initialisations nécessaires en début de programme
:
aaaaaaaa{
aaaaaaaaaaa(…)
(1)aaaaaaaainstall_timer();
(2)aaaaaaaaLOCK_VARIABLE(t_jeu);
aaaaaaaaaaaLOCK_VARIABLE(t_piece);
aaaaaaaaaaaLOCK_FUNCTION(Timer);
(3)aaaaaaaainstall_int_ex(Timer,
BPS_TO_TIMER(60));
aaaaaaaaaaa(…)
aaaaaaaa}
(1)
Appel de la fonction donnée par Allegro et qui permet d’avoir
un interrupt handler
(2) Toutes les variables et le code utilisés dans les interrupt
handlers doivent être "locked",
verrouillées
(3) Le nombre d’appels par seconde (Beats Per Second) de notre fonction
« Timer() ». Ici
60 appels par seconde.
4. Mise en
œuvre dans le main et la boucle d’événements
Initialisation
de l’environnement, timer et ressources au démarrage
aaaaavoid
init_env()
aaaaa{
(1)aaaaaasrand(time(NULL));
(2)aaaaaaallegro_init();
aaaaaaaaainstall_keyboard();
(3)aaaaaainstall_timer();
aaaaaaaaaLOCK_VARIABLE(t_jeu);
aaaaaaaaaLOCK_VARIABLE(t_piece);
aaaaaaaaaLOCK_FUNCTION(Timer);
aaaaaaaaainstall_int_ex(Timer,
BPS_TO_TIMER(60));
(4)aaaaaaatext_mode(-1);
(5)aaaaaaaset_color_depth(16);
aaaaaaaaaaif
(set_gfx_mode( GFX_AUTODETECT_WINDOWED, ECRAN_X,ECRAN_Y,0,0) !=0 ){
aaaaaaaaaaaaaallegro_message("erreur
gfx_mode");
aaaaaaaaaaaaaallegro_exit()
;
aaaaaaaaaaaaaexit(1);
aaaaaaaaaa}
(6)aaaaaaaPAGE=create_bitmap(ECRAN_X,ECRAN_Y);
(7)aaaaaaaGRPH=load_datafile(".//graphic.dat");
aaaaaaaaaaif(!GRPH
|| !PAGE){
aaaaaaaaaaaaallegro_message("erreur
GRPH ou PAGE");
aaaaaaaaaaaaallegro_exit()
;
aaaaaaaaaaaaexit(1);
aaaaaaaaaa}
aaaaa}
(1)
Fonction du C standard pour l’initialisation d’une liste de
nombres pseudo-aléatoires
(2) Fonction d’initialisation pour l’utilisation de l’environnement
allegro et fonction
d’initialisation pour l’utilisation du clavier sous Allegro.
(3) Tout ce qui concerne la mise en place d’un interrupt handler
et que nous avons vu
précédemment.
(4) Choix qui concerne l’affichage du texte, ici sur fond transparent.
(5) Choix de la profondeur de couleur, ici 16 bits et Définition
du mode graphique : mode
fenêtre, résolution donnée par les macros ECRAN_X
et ECRAN_Y et ensuite contrôle
d’erreurs
(6) Création bitmap globale PAGE destinée à servir
de double buffer.
(7) Chargement des ressources stockées dans le fichier «
graphique.dat » et contrôle
d’erreurs
Le main,
initialisations, boucle d’événements
aaaaa
int main()
aaaaa{
(1)aaaint
i, desc, speed=0, nb_lignes=0, fin=0;
aaaaqaint
lignes[TY];
aaaaaaint
niveau[25]= {70, 65, 60, 55, 50, 45, 40, 35, 30, 26, 22, 19, 16, 14, 12,
10, 8, 7, 6, 5, 4, 3, 2, 1, 0};
(2)aaaaaainit_env()
;
aaaaaaaacourante
= init_piece();
aaaaaaaasuivante
= init_piece();
aaaaaaaamemset(PLAN,0,TX*TY*sizeof(char));
(3)aaaaawhile
(!fin){
(4)aaaaaaaaif
(key[KEY_ESC])
aaaaaaaaaaaaaafin=1;
(5)aaaaaaaacntl_affichage();
(6)aaaaaaaawhile
(t_jeu>0){
aaaaaaaaaaaaaat_jeu--;
(7)aaaaaaaaaaamove();
(8)aaaaaaaaaaaif
(t_piece>niveau[speed]){
aaaaaaaaaaaaaaaaat_piece=0;
(9)aaaaaaaaaaaaaadesc=descendre(courante);
(10)aaaaaaaaaaaaaif
(desc==-1)
aaaaaaaaaaaaaaaaaaaafin=1;
aaaaaaaaaaaaaaaaaelse
if (desc==0){
aaaaaaaaaaaaaaaaaaaafixer_piece(courante);
aaaaaaaaaaaaaaaaaaaainit_piece_suivante();
aaaaaaaaaaaaaaaaaaaaif
(i=compter_lignes_finies(lignes)){
aaaaaaaaaaaaaaaaaaaaaaaeffacer_lignes_finies(lignes);
aaaaaaaaaaaaaaaaaaaaaaanb_lignes+=i;
aaaaaaaaaaaaaaaaaaaaaaaspeed=nb_lignes/2;
aaaaaaaaaaaaaaaaaaaaaaaif
(speed>=25){
aaaaaaaaaaaaaaaaaaaaaaaaaa//prévoir
qqchose : jeu gagné
aaaaaaaaaaaaaaaaaaaaaaa}
aaaaaaaaaaaaaaaaaaaa}
aaaaaaaaaaaaaaaaa}
aaaaaaaaaaaaaa}
aaaaaaaaaaa}
aaaaaaaa}
(11)aaaaanettoyage_sortie();
aaaaaaaaareturn
0;
aaaaa}
aaaaaEND_OF_MAIN();
(1)
Déclarations, initialisation des variables nécessaires à
partir du main. « desc » sert à récupérer
la valeur de retour de la fonction de descente de la piece courante. «
speed » donne le niveau actuel de la partie c'est-à-dire
un indice dans le tableau « niveau » des temps d’attente
de plus en plus court pour la descente de la pièce. « nb_lignes
» va compter tout au long de la partie le nombre de lignes finies
et plus il sera grand plus le niveau va augmenter, en fait la valeur de
la variable speed est calculée à partir du nombre des lignes
finies. « fin » permet de contrôler la boucle principale
et d’aasurer la fin de la partie. Le tableau « lignes »
sert à compter les lignes finies.
(2)
Toutes les initialisations, appel de la fonction init_env() vue précédement
mais également, choix d’une pièce courante et d’une
pièce suivante, mise à 0 de toutes les possitions de la
matrice PLAN, la zone de jeu.
(3)
Boucle d’événements, prend fin si la variable fin
devient différente de 0
(4)
Possibilité de mettre fin à la boucle principale en appuyant
sur la touche echap.
(5)
Appel de la fonction de contrôle de l’affichage.
(6)
Boucle pour la régulation dans le temps du jeu, les instructions
ne sont accessibles que si la variable t_jeu a été incrémentée
via la fonction Timer() et l’interrupt handler. Si tel est le cas
une fois dans la boucle la première instruction consiste à
décrémenter t_jeu.
(7)
Contrôle du mouvement des pièces.
(8)
La rapidité de la descente automatique de la pièce est en
fonction du niveau contrôlée par l’incrémentation
régulière en parallèle de la variable t_pièce.
(9)
Le retour de la descente automatique est récupéré
dans la variable desc.
(10)
S’il est égal à -1 (impossibilité de faire
descendre une nouvelle pièce) la partie est finie.
S’il est égal à 0 la pièce est bloquée
et il faut passer à la pièce suivante. Dans ce cas il faut
également compter les lignes finies et les enlever. La variable
« i » stocke le nombre de lignes finies. Le nombre total des
lignes finies pendant la partie est incrémenté ensuite de
« i ». Le niveau a peut-être augmenté et l’indice
« speed » pour le tableau « niveau » est mis à
jour (deux lignes finies font progresser de un). Dans le cas peu probable
ou quelqu’un attendrait le niveau 25, il serait amusant de créer
une petite anime de félicitation.
(11) Juste la déallocation des ressources, PAGE et GRPH. Détail
de la fonction nettoyage_sortie() :
aaaaavoid
nettoyage_sortie()
aaaaa{
aaaaaaaaaif
(GRPH)
aaaaaaaaaaaaunload_datafile(GRPH);
aaaaaaaaaif
(PAGE)
aaaaaaaaaaaadestroy_bitmap(PAGE);
aaaaa}
|