Exemple d'utilisation d'une TDrawGrid : ( Un démineur )
Pour notre exemple nous allons construire un jeu connu de tous, "le démineur".
Dans l'exemple il aura 16 x 16 cases et 20 mines.
Nous utiliserons pour cela une DrawGrid où nous dessinerons dans chaque case
leur valeur. (Cachée, Mine, Valeur, Vide.)
Sur une Form (Form1) poser :
- Une DrawGrid (DrawGrid1) avec les propriétés :
Height à 403
Width à 403
DefaultColWidth à 24
DefaultRawWidth à 24
ColCount à 16
RawCount à 16
FixedCols à 0
FixedRows à 0
ScrollBars à ssNone
- Un Bouton (Button1) avec la propriété :
Caption à "Nouvelle partie"
Unit1.h :
Déclaration des données membre et méthodes.
La DrawGrid n'ayant pas d'emplacement pour stocker les valeurs de ses cases,
nous les stockerons donc dans un tableau.(TabVal[16][16])
Le 2eme tableau (TabCase[16][16]) nous indiquera si la case est : cachée, visible
ou en cours de traitement des cases vide adjacentes.
private: // Déclarations de l'utilisateur
char TabCase[16][16];
char TabVal[16][16];
int NbMine;
bool PasFini;
void __fastcall Initialisation();
void __fastcall Decouvre(int x,int y);
Unit1.cpp :
Sur l'événement OnCreate de Form1
Affectation du nombre de mines à NbMine et Initialisation.
void __fastcall TForm1::FormCreate(TObject *Sender)
{
NbMine = 20;
Initialisation();
}
Définition de la méthode Initialisation
C'est ici que l'on rempli le tableau avec les mines et que l'on calcule
la valeur des cases. Puis rafraîchissement de la DrawGrid par sa
méthode Invalidate().
void __fastcall TForm1::Initialisation()
{
int x, y;
PasFini = true ;
ZeroMemory(TabCase, sizeof(TabCase));
//Poser les mines aléatoirement
ZeroMemory(TabVal, sizeof(TabVal));
randomize();
for (int z=0 ; z < NbMine ; z++)
{
x = rand() % 16 ;
y = rand() % 16 ;
TabVal[x][y] = 10;
}
//Remplir le tableau de valeur
for (x=0 ; x < 16; x++)
for (y=0 ; y < 16; y++)
{
if (TabVal[x][y] >= 10)
{
if(y<=14)TabVal[x][y+1]++;
if(y>0)TabVal[x][y-1]++;
if(x<=14)TabVal[x+1][y]++;
if(x<=14 && y<=14)TabVal[x+1][y+1]++;
if(x<=14 && y>0)TabVal[x+1][y-1]++;
if(x>0)TabVal[x-1][y]++;
if(x>0 && y<=14)TabVal[x-1][y+1]++;
if(x>0 && y>0)TabVal[x-1][y-1]++;
}
}
//Rafraîchir la grille
DrawGrid1->Invalidate();
}
Sur l'événement OnClick de Button1.
Initialisation pour une nouvelle partie.
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Initialisation();
}
Evénement OnDrawCell de DrawGrid1:
C'est ici que l'on dessine. Cette méthode est appelée pour chaque case.
La case en cours de traitement est reçue pas les paramètres ACol et ARow et
sa position et ses dimensions par le paramètre Rect. Le paramètre State
renvoie son état, par exemple si elle a le focus. (Voir aide BCB rubrique TGridDrawState.)
Pour dessiner nous nous servirons du Canvas de la DrawGrid. Nous passerons
sa fonte en Gras, la couleur de sa Brush en clBtnFace pour les cases cachées et en
clWindow pour les cases visibles, qui nous servira à rempir les cases avec la méthode
FillRect. Nous utiliserons la fonction de l'API Windows
DrawEdge pour dessiner le relief des cases cachées. Pour le dessin des cases
visible nous utiliserons la méthode TextRect du Canvas. Elle a l'avantage
par rapport à TextOut de ne pas déborder de la case. (Dans cet exemple on aurait
pu utiliser TextOut.) Puis on fini par effacer le rectangle de focus avec la
méthode DrawFocusRect du canvas. (Pour effacer un rectangle de focus il suffit
de le dessiner une seconde fois.)
void __fastcall TForm1::DrawGrid1DrawCell(TObject *Sender, int ACol,
int ARow, TRect &Rect, TGridDrawState State)
{
//Texte en Gras
DrawGrid1->Canvas->Font->Style = DrawGrid1->Canvas->Font->Style << fsBold;
if(TabCase[ACol][ARow]==0) //Dessiner les cases cachées.
{
DrawGrid1->Canvas->Brush->Color = clBtnFace;
DrawGrid1->Canvas->FillRect(Rect);
DrawEdge(DrawGrid1->Canvas->Handle,&Rect,EDGE_RAISED,BF_RECT);
}
else //Dessiner les cases découvertes.
{
DrawGrid1->Canvas->Brush->Color = clWindow;
DrawGrid1->Canvas->FillRect(Rect);
if (TabVal[ACol][ARow] >= 10) DrawGrid1->Canvas->
TextRect(Rect, Rect.Left+7, Rect.Top+7, "M");
if(TabVal[ACol][ARow]==1) DrawGrid1->Canvas->Font->Color = clGreen;
if(TabVal[ACol][ARow]==2) DrawGrid1->Canvas->Font->Color = clBlue;
if(TabVal[ACol][ARow]>2 && TabVal[ACol][ARow]<9)
DrawGrid1->Canvas->Font->Color = clRed;
if (TabVal[ACol][ARow] >= 0 && TabVal[ACol][ARow] < 9) DrawGrid1->Canvas->
TextRect(Rect, Rect.Left+7, Rect.Top+7,
IntToStr(TabVal[ACol][ARow]));
if (TabVal[ACol][ARow] == 0 ) DrawGrid1->Canvas->
TextRect(Rect, Rect.Left+7, Rect.Top+7, "");
//Cette ligne n'écris rien, mais sans elle il y a des problémes
//pour éffacer le rectangle de focus.
}
//Effacer le rectangle de focus.
if(State.Contains(gdFocused)) DrawGrid1->Canvas->DrawFocusRect(Rect);
}
Sur l'événement OnClick de DrawGrid1 on récupère la case cliquée
dans les propriétés Col et Row de la DrawGrid. Elles indiquent la case
sélectionnée, et comme en cliquant sur la case on l'a sélectionnée, alors
le tour est joué. Le reste du code sert pour le traitement du jeu.
Nous utilisons la méthode Invalidate() de la DrawGrid là aussi pour la
rafraîchir.
void __fastcall TForm1::DrawGrid1Click(TObject *Sender)
{
if (PasFini == false) return;
bool fin=true;
int x=DrawGrid1->Col;
int y=DrawGrid1->Row;
TabCase[x][y]=1 ;
//Tout découvrir si mine
if (TabVal[x][y] >=10)
{
for (int x=0 ; x < 16; x++)
for (int y=0 ; y < 16; y++)
TabCase[x][y]=1 ;
PasFini = false;
DrawGrid1->Invalidate();
ShowMessage("Vous avez perdu");
return;
}
//Découvrir les case vides et adjacente
if (TabVal[x][y] == 0)
{
Decouvre(x,y);
}
do
{
fin = false;
x=0;
while (x<=15)
{
y=0;
while (y<=15)
{
if (TabCase[x][y] == 2)
{
TabCase[x][y] = 1;
Decouvre(x,y);
fin = true;
}
y++;
}
x++;
}
}
while (fin);
//Rafréchir la grille
DrawGrid1->Invalidate();
//Gagné
PasFini=false;
for (int x=0 ; x < 16; x++)
for (int y=0 ; y < 16; y++)
if(TabCase[x][y]==0 && TabVal[x][y]<10) PasFini = true ;
if (PasFini == false) ShowMessage("Vous avez gagné");
}
Définition de la méthode Decouvre qui sert aussi au traitement du jeu.
void __fastcall TForm1::Decouvre(int x,int y)
{
if(y<=14)
{
if (TabVal[x][y+1]>0 || TabCase[x][y+1]==1) TabCase[x][y+1]=1;
else TabCase[x][y+1]=2;
}
if(y>0)
{
if (TabVal[x][y-1]>0 || TabCase[x][y-1]==1) TabCase[x][y-1]=1;
else TabCase[x][y-1]=2;
}
if(x<=14)
{
if (TabVal[x+1][y]>0 || TabCase[x+1][y]==1) TabCase[x+1][y]=1;
else TabCase[x+1][y]=2;
}
if(x<=14 && y<=14)
{
if (TabVal[x+1][y+1]>0 || TabCase[x+1][y+1]==1) TabCase[x+1][y+1]=1;
else TabCase[x+1][y+1]=2;
}
if(x<=14 && y>0)
{
if (TabVal[x+1][y-1]>0 || TabCase[x+1][y-1]==1) TabCase[x+1][y-1]=1;
else TabCase[x+1][y-1]=2;
}
if(x>0)
{
if (TabVal[x-1][y]>0 || TabCase[x-1][y]==1) TabCase[x-1][y]=1;
else TabCase[x-1][y]=2;
}
if(x>0 && y<=14)
{
if (TabVal[x-1][y+1]>0 || TabCase[x-1][y+1]==1) TabCase[x-1][y+1]=1;
else TabCase[x-1][y+1]=2;
}
if(x>0 && y>0)
{
if (TabVal[x-1][y-1]>0 || TabCase[x-1][y-1]==1) TabCase[x-1][y-1]=1;
else TabCase[x-1][y-1]=2;
}
}
Dans cette application la rotation de la roulette de la souris n'est pas souhaitée.
Alors pour l'invalider nous allons rajouter ces lignes de code dans les événements
OnMouseWheelUp et OnMouseWheelDown.
void __fastcall TForm1::DrawGrid1MouseWheelDown(TObject *Sender,
TShiftState Shift, TPoint &MousePos, bool &Handled)
{
Handled = true;
}
void __fastcall TForm1::DrawGrid1MouseWheelUp(TObject *Sender,
TShiftState Shift, TPoint &MousePos, bool &Handled)
{
Handled = true;
}
Le code précédant a été élaboré sous BCB6.
Aprés un test sous BCB4 il a fallu rajouter la ligne suivante dans Unit1.cpp
(Pour randomize et rand.)
#include <stdlib.h>
Dans cet exemple je n'ai pas commenté la partie traitant du jeu (ce n'était pas le but
de ce tutoriel), mais seulement celle traitant de la TDrawGrid.
Je vous souhaite bon amusement. Et si c'est trop facile rajoutez des mines.
CGi
|