Developpez.com - C
X

Choisissez d'abord la catégorieensuite la rubrique :



Un démineur dans une TDrawGrid.

Par CGi

Dernière modification le 3 décembre 2002


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





C/C++
  Les pointeurs du C/C++.   Les listes chaînées.             Liste simple.             Liste triée.             Liste double.   Les arbres.   Les tas.   Le C orienté objets ?

  1 - La fenêtre principale.   2 - Contrôles et messages.   3 - Les commandes.   4 - Dialogue std.   5 - Contexte de périph.   6 - Dessiner.   7 - Les ressources.   8 - Dialogue perso.   9 - Dialogue comm.   10 - Les accélérateurs.

Assembleur
  Assembleur sous Visual C++.

C++ BUILDER
  Trucs et astuces.   Composant.   TRichEdit.   TDrawGrid.   Application MDI.   TThread.   wxWidgets.   Style Win XP.

  Première application.   Construire un menu.   Dessiner.   Sisers, Timers...   Dialogues standards.   Dialogues perso.

DotNet
  Composant C# Builder.   Contrôle WinForm.   Application MDI.

Java
  Applet java.





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.

Contacter le responsable de la rubrique C