Koło informatyczne 2012

Gra w 15

obrazek

 

Popularna gra w 15 składa się z planszy, na której znajduje się 15 ruchomych kwadratów oraz jedno puste pole. Kwadraty są ponumerowane kolejno od 1 do 15. Możne je przesuwać tylko na puste pole. Celem gry jest ułożenie pomieszanych kwadratów w kolejności od 1 do 15 z pustym polem w prawym dolnym narożniku.

Na dzisiejszych zajęciach zaprogramujemy prostą wersję tej gry, wykorzystując nowy komponent - DrawGrid. Jest to element graficzny, który pozwala tworzyć różnego rodzaju obiekty tabelaryczne, czyli składające się z komórek. Posiada on wiele różnych własności. Nas będzie interesować własność Canvas, która jest powierzchnią graficzną. Na powierzchni graficznej możemy rysować za pomocą odpowiednich funkcji składowych, które Canvas udostępnia.

Tworzymy nowy projekt

Uruchom środowisko Borland C++ Builder. Następnie zapisz w nowym katalogu pliki projektu za pomocą opcji menu:

 

FileSave Project As...

 

Plik modułu Unit1.cpp zapisz bez zmiany nazwy. Plik projektu Project1.bpr zapisz pod nazwą Puzzle15.

 

Tworzymy interfejs gry w 15

Najpierw zaprojektujemy odpowiedni interfejs dla naszej aplikacji. Rozpoczniemy od zmodyfikowania kilku własności okna aplikacji w Object Inspector (zmieniane własności podajemy w kolejności alfabetycznej, idąc od góry w dół):

Okno aplikacji

BorderIcons→biMaximize = false
Okienka nie będzie można powiększać na cały ekran.

BorderStyle = bsSingle
Rozmiarów okna użytkownik nie będzie mógł zmieniać.

Caption = Gra w 15
Tytuł okna programu

Name = frmPuzzle15
Nazwa egzemplarza okna aplikacji w naszym programie.

Position = poScreenCenter
Okno aplikacji będzie się pojawiało zawsze na środku ekranu.

DrawGrid

Wybierz zakładkę Additional na palecie komponentów. Następnie wskaż myszką komponent DrawGrid i kliknij go dwukrotnie. Komponent pojawi się w oknie aplikacji.

ColCount = 4
Określa liczbę kolumn na naszej planszy. Gra w 15 będzie rozgrywana jest na planszy 4 x 4.

DefaultColWidth = 80
Określa szerokość kolumny w pikselach.

DefaultDrawing = false
Rezygnujemy ze standardowego rysowania komórek naszej planszy - wszystkie rysunki wykonamy sobie sami.

DefaultRowHeight = 80
Określa wysokość wiersza. Po wprowadzeniu tej wartości komórki planszy staną się kwadratowe.

FixedCols = 0
Liczba tzw. stałych kolumn, które są rysowane przy lewej krawędzi. Nie potrzebujemy ich, dlatego wpisujemy tutaj 0.

FixedRows = 0
Liczba tzw. stałych wierszy, które są rysowane u góry. Tych też nie potrzebujemy, zatem 0.

Font→Color = clMaroon
Własność Font pozwala określić standardową czcionkę dla naszej planszy. Ustawiamy kolor czcionki na Kasztanowy. Możesz poeksperymentować później z innymi kolorami.

Font→Name = Times New Roman
Krój czcionki

Font→Size = 32
Rozmiar czcionki.

Font→Style→fsBold = true
Styl czcionki - pogrubiona

Height = 328
Wysokość planszy. Została tak dobrana, aby wszystkie pola były widoczne.

Left = 8
Pozycja lewej krawędzi planszy na oknie aplikacji. Gdy wprowadzisz tę wartość, plansza przesunie się do lewej krawędzi okna.

Name = dgdPuzzle
Pod tą nazwą będzie widoczny w programie obiekt planszy, dgd oznacza DrawGrid.

RowCount = 4
Liczba wierszy.

ScrollBars = ssNone
Określa paski przewijania treści naszej planszy. Ponieważ wszystkie kolumny i wiersze będą u nas widoczne, wybieramy brak pasków przewijania.

Top = 8
Położenie górnej krawędzi planszy w oknie aplikacji.

Width = 328
Określa szerokość planszy.

Gdy wprowadzisz wszystkie powyższe własności, zmniejsz szerokość okna aplikacji, tak aby obejmowała jedynie naszą planszę wraz z niewielkim marginesem po prawej stronie:

obrazek

Na palecie komponentów wróć do zakładki Standard i umieść pod planszą dwa przyciski. Następnie dopasuj odpowiednio wysokość okna aplikacji.

obrazek

Przycisk lewy

Caption = Start

Name = btnStart

 

Przycisk prawy

Caption = Czyść

Name = btnClear

Interfejs aplikacji jest gotowy:

obrazek

Obsługa zdarzeń

Najpierw musimy się zastanowić, w jaki sposób będziemy reprezentować w pamięci komputera naszą planszę. Component DrawGrid pomoże ją tylko wyświetlić. Ponumerujmy pola planszy od 1 do 16 (u góry i z boku zapisaliśmy odpowiednio numery kolumn i wierszy):

 

  0 1 2 3
0 1 2 3 4
1 5 6 7 8
2 9 10 11 12
3 13 14 15 16

 

Taką strukturę możemy w prosty sposób odwzorować w tablicy dwuwymiarowej. Zatem na początku programu (pod definicją okna) dopisujemy:

 

TfrmPuzzle15 *frmPuzzle15;
int g[4][4]; // zawartość planszy gry
//---------------------------------------------------------------------------

Tablica ta powinna być zainicjowana w momencie startu programu, aby w oknie mogła się pojawić zawartość planszy. Przy tym umawiamy się, że procedura wyświetlająca planszę będzie wyświetlała liczby o 1 większe, niż są faktycznie w komórkach. Natomiast wartość 16 będzie miała znaczenie pustego pola i nie zostanie wyświetlona. Tablicę zainicjujemy w funkcji obsługi zdarzenia onCreate głównego okna aplikacji. Kliknij myszką pusty obszar okna lub z Object TreeView wybierz obiekt frmPuzzle15. Spowoduje to, iż w Object Inspector pojawią się własności okna. Teraz wybierz zakładkę Events i kliknij dwukrotnie po prawej stronie zdarzenia onCreate. Środowisko utworzy pustą funkcję obsługi tego zdarzenia. Wpisz do niej poniższy kod:

 

//---------------------------------------------------------------------------
void __fastcall TfrmPuzzle15::FormCreate(TObject *Sender)
{
  srand(time(NULL)); // ustawiamy generator liczb pseudolosowych

  for(int i = 0; i < 4; i++)    // wiersze
    for(int j = 0; j < 4; j++)  // kolumny
      g[i][j] = 1 + 4 * i + j;  // zawartość komórek

  dgdPuzzle->Canvas->Font = dgdPuzzle->Font; // kopiujemy czcionkę

  dgdPuzzle->Refresh();         // odświeżamy planszę
}
//---------------------------------------------------------------------------

 

W procedurze również kopiujemy wybraną dla obiektu DrawGrid czcionkę do czcionki używanej na płaszczyźnie rysunkowej Canvas. W ten sposób procedury rysowania tekstu dla Canvas będą wyświetlały czcionki w takim samym kroju, jaki wybraliśmy w czasie projektowania planszy. Wywołanie funkcji Refresh() spowoduje odświeżenie planszy, czyli przerysowanie jej elementów.

Wybierz obiekt dgdPuzzle (klikając go myszką lub wybierając z Object TreeView). Na zakładce Events w Object Inspector kliknij dwukrotnie obok zdarzenia onDrawCell. Zdarzenie to powstaje wtedy, gdy komponent musi przerysować jedną ze swoich komórek. W edytorze otrzymasz pustą funkcję obsługi tego zdarzenia:

 

//---------------------------------------------------------------------------

void __fastcall TfrmPuzzle15::dgdPuzzleDrawCell(TObject *Sender, int ACol,
      int ARow, TRect &Rect, TGridDrawState State)
{

}
//---------------------------------------------------------------------------

 

Funkcja otrzymuje kilka użytecznych dla nas parametrów:

 

ACol, ARow  -  numer kolumny i wiersza, dla których odnosi się dane zdarzenie
Rect  - obiekt prostokąt, który definiuje obszar komórki. Obiekt ten posiada kilka pól:

Rect.Left - współrzędna lewej krawędzi prostokąta na obszarze Canvas
Rect.Right - współrzędna prawej krawędzi prostokąta + 1
Rect.Top - współrzędna górnej krawędzi
Rect.Bottom - współrzędna dolnej krawędzi + 1

Rect.Width() - funkcja zwracająca szerokość prostokąta
Rect.Height() - funkcja zwracająca wysokość prostokąta

 

Przepisz do funkcji poniższy kod:

 

//---------------------------------------------------------------------------
void __fastcall TfrmPuzzle15::dgdPuzzleDrawCell(TObject *Sender, int ACol,
      int ARow, TRect &Rect, TGridDrawState State)
{
   if(g[ARow][ACol] == 1 + 4 * ARow + ACol)
        dgdPuzzle->Canvas->Brush->Color = clWhite;  // pozycja dobra
   else dgdPuzzle->Canvas->Brush->Color = clYellow; // pozycja zła

   if(g[ARow][ACol] == 16) dgdPuzzle->Canvas->Brush->Color = clBtnFace;

   dgdPuzzle->Canvas->Rectangle(Rect); // wypełniamy komórkę kolorem

   if(g[ARow][ACol] != 16)
   {
     AnsiString s = IntToStr(g[ARow][ACol]);
     int x = Rect.Left + (Rect.Width()  - dgdPuzzle->Canvas->TextWidth(s))  / 2;
     int y = Rect.Top  + (Rect.Height() - dgdPuzzle->Canvas->TextHeight(s)) / 2;
     dgdPuzzle->Canvas->TextOutA(x,y,s);
   }
}
//---------------------------------------------------------------------------

 

Na początku określamy kolor wypełnienia komórki. Jeśli komórka zawiera liczbę na właściwej pozycji, to kolor będzie biały. Jeśli przechowywana przez komórkę liczba nie jest na swojej pozycji, to kolor będzie żółty. W przypadku gdy komórka zawiera 16, kolorem będzie kolor tła przycisków - jest to puste miejsce. Kolor wypełnienia ustawiamy we własności Brush należącej do Canvas.

Dalej sprawdzamy, czy komórka zawiera liczbę od 1 do 15. Jeśli tak, liczbę wyświetlimy wewnątrz komórki. Najpierw zamieniamy wartość liczby na tekst i umieszczamy go w łańcuchu s. Obliczamy współrzędne x  i y  lewego górnego narożnika tekstu. Chcemy, aby tekst zawsze pojawiał się na środku komórki. Dlatego od rozmiaru prostokąta komórki odejmujemy rozmiar tekstu i dzielimy wynik przez 2. Otrzymane przesunięcie dodajemy do współrzędnej prostokąta. Na otrzymanych w ten sposób współrzędnych x  i y  zostanie wydrukowana liczba z komórki.

Gdy uruchomisz teraz program, zobaczysz naszą planszę:

obrazek

Teraz dokonamy mieszania kwadratów na planszy. Nie można tego zrobić w dowolny sposób, ponieważ układanki w pewnych sytuacjach nie dałoby się ułożyć. Musimy wykonać symulację przestawiania kwadratów. W oknie aplikacji kliknij dwukrotnie przycisk Start i wpisz poniższy kod do procedury obsługi kliknięcia tego przycisku:

 

//---------------------------------------------------------------------------
void __fastcall TfrmPuzzle15::btnStartClick(TObject *Sender)
{
  int x,y,i,j;

  // szukamy pustego miejsca
  for(i = 0; i < 4; i++)
    for(j = 0; j < 4; j++)
      if(g[i][j] == 16)
      {
        y = i; x = j;
      }

  // symulujemy przesuwanie
  for(i = 0; i < 10000; i++)
    switch(rand() % 4)  // generujemy przypadkowy kierunek
    {
      case 0: if(y)     // w górę
              {
                g[y][x] = g[y-1][x]; g[y-1][x] = 16;
                y--;
              }
              break;
      case 1: if(y < 3) // w dół
              {
                g[y][x] = g[y+1][x]; g[y+1][x] = 16;
                y++;
              }
              break;
      case 2: if(x)     // w lewo
              {
                g[y][x] = g[y][x-1]; g[y][x-1] = 16;
                x--;
              }
              break;
      case 3: if(x < 3) // w prawo
              {
                g[y][x] = g[y][x+1]; g[y][x+1] = 16;
                x++;
              }
              break;
    }
    dgdPuzzle->Refresh();
}
//---------------------------------------------------------------------------

 

Gdy uruchomisz aplikację i klikniesz w przycisk Start, to zawartość planszy zostanie pomieszana:

obrazek

Żółte kwadraty są na niewłaściwych pozycjach. Białe kwadraty oznaczają cyfry na właściwych pozycjach - tutaj 9 i 11.

Teraz szybko dorobimy obsługę kliknięcia przycisku Czyść. Nie musimy pisać żadnego nowego kodu - całość zawiera funkcja obsługi zdarzenia onCreate dla naszego okna aplikacji. Wybierz przycisk btnClear (klikając go jeden raz myszką lub wybierając btnClear z okna Object TreeView). Teraz w oknie Object Inspector wybierz zakładkę Events, kliknij jednokrotnie obok zdarzenia onClick, a następnie kliknij w strzałkę w dół, aby rozwinąć listę dostępnych funkcji obsługi tego zdarzenia. Z listy wybierz FormCreate. Po uruchomieniu aplikacji kliknięcie przycisku Czyść spowoduje przejście pola gry w stan wyjściowy.

Pozostała nam obsługa przesuwania kwadratów na planszy. Wybierz planszę i na zakładce Events w Object Inspector kliknij dwukrotnie po prawej stronie zdarzenia onSelectCell. Środowisko utworzy w edytorze funkcję obsługi tego zdarzenia:

 

//---------------------------------------------------------------------------
void __fastcall TfrmPuzzle15::dgdPuzzleSelectCell(TObject *Sender,
      int ACol, int ARow, bool &CanSelect)
{

}
//---------------------------------------------------------------------------

 

Zdarzenie onSelectCell pojawia się, gdy użytkownik kliknie myszką jedną z komórek obiektu DrawGrid (lub użyje klawiszy kursora, jednakże tego tutaj nie obsługujemy, chociaż działa po wcześniejszym wybraniu pustej komórki myszką). Funkcja obsługi zdarzenia otrzymuje informację o położeniu tej komórki w parametrach ACol (kolumna) i ARow (wiersz). Naszym zadaniem jest przesunięcie wybranego kwadratu na puste pole. Wynika z tego, iż wybrana komórka musi sąsiadować z komórką zawierającą liczbę 16, która u nas oznacza pusty kwadrat. Do funkcji wpisz poniższy kod:

 

//---------------------------------------------------------------------------
void __fastcall TfrmPuzzle15::dgdPuzzleSelectCell(TObject *Sender,
      int ACol, int ARow, bool &CanSelect)
{
  if(ACol && g[ARow][ACol-1] == 16) // w lewo?
  {
    g[ARow][ACol-1] = g[ARow][ACol]; g[ARow][ACol] = 16;
  }
  else if(ACol < 3 && g[ARow][ACol+1] == 16) // w prawo?
  {
    g[ARow][ACol+1] = g[ARow][ACol]; g[ARow][ACol] = 16;
  }
  else if(ARow && g[ARow-1][ACol] == 16) // w górę?
  {
    g[ARow-1][ACol] = g[ARow][ACol]; g[ARow][ACol] = 16;
  }
  else if(ARow < 3 && g[ARow+1][ACol] == 16) // w dół?
  {
    g[ARow+1][ACol] = g[ARow][ACol]; g[ARow][ACol] = 16;
  }

  dgdPuzzle->Refresh();
}
//---------------------------------------------------------------------------

 

Gra jest gotowa. Miłej zabawy.

Dla ambitnych:

Zastanów się, jak przy wykorzystaniu komponentu Label oraz funkcji IntToStr() można zrealizować prosty licznik wykonanych posunięć.

 


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe