I. 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 2e tableau (TabCase[16][16]) nous indiquera si la case est : cachée, visible ou en cours de traitement des cases vides 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 remplit 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
(
);
}
Évé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 à remplir 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 visibles, 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 finit 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'écrit rien, mais sans elle, il y a des problèmes
//pour effacer 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 cases vides et adjacentes
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);
//Rafraî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édent 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.