IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Un démineur dans une TDrawGrid

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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.)

Image non disponible

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.

 
Sélectionnez
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.

 
Sélectionnez
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().

 
Sélectionnez
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.

 
Sélectionnez
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.)

 
Sélectionnez
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.

 
Sélectionnez
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.

 
Sélectionnez
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

 
Sélectionnez
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.

CGi

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Copyright 2002-2016 CGi - Tous droits réservés CGi. Toutes reproduction, utilisation ou diffusion de ce document par quelque moyen que ce soit autre que pour un usage personnel doit faire l'objet d'une autorisation écrite de la part de l'auteur, propriétaire des droits intellectuels.
Les codes sources de ce document sont fournis en l'état. L'utilisateur les utilise à ses risques et périls, sans garantie d'aucune sorte de la part de l'auteur. L'auteur n'est responsable d'aucun dommage subi par l'utilisateur pouvant résulter de l'utilisation ou de la distribution des codes sources de ce document.
De la même façon, l'auteur n'est en aucun cas responsable d'une quelconque perte de revenus ou de profits, ou de données, ou de tous dommages directs ou indirects, susceptibles de survenir du fait de l'utilisation des codes sources de ce document, quand bien même l'auteur aurait été averti de la possibilité de tels dommages. L'utilisation des codes sources de ce document vaut acceptation par l'utilisateur des termes de la licence ci-dessus.