P018 - Gra zręcznościowa - DOMINO
Programy uruchomiono w środowisku Bloodshed Dev-C++ 4.9.9.2

Uwaga, program p018 wykorzystuje bibliotekę newconio, którą stworzyliśmy na wcześniejszych zajęciach koła. Do projektu należy dołączyć plik newconio.cpp oraz plik nagłówkowy newconio.h. Bez tych plików program nie uruchomi się.

// I Liceum Ogólnokształcące
// im. K. Brodzińskiego
// w Tarnowie
//--------------------------
// Koło informatyczne 2006/7
//--------------------------
// Program: P018-01
//--------------------------

#include "newconio.h"
#include <iostream>

using namespace std;

const int SMAX = 80 * 25; // maksymalny rozmiar stosu

// Definicja klasy obsługującej stos
//----------------------------------
class stos
{
  private:

    int x[SMAX], y[SMAX];           // przechowuje współrzędne; 
    int ws;                         // wskaźnik stosu

  public:

    stos() // konstruktor zeruje stos
    {
      ws = 0;
    }
    
    int rozmiar() // zwraca ilość elementów na stosie
    {
      return ws;
    }
    
    void zapisz(int wx, int wy) // zapisuje współrzędne na stosie
    {
      x[ws] = wx; y[ws] = wy; ws++;
    }
    
    void czytaj(int &wx, int &wy) // odczytuje współrzędne ze stosu
    {
      ws--; wx = x[ws]; wy = y[ws];
    }
};

// Wyświetla ekran tytułowy gry
//-----------------------------
void ekran_tytulowy()
{
  textattr(7); clrscr();
  textcolor(15); center(10, _pl("KOŁO INFORMATYCZNE '2007"));
  textcolor(14); center(12, "I LO w Tarnowie");
  textcolor(13); center(14, _pl("D O M I N O"));
  textcolor(7);  center(49, _pl("--- Naciśnij dowolny klawisz ---"));
  while(!getch()) ;
}

// Wyświetla planszę gry
//----------------------
void plansza(int k, int c)
{  
  textattr(0x80); clrscr();
  textcolor(0);  gotoxy(13,0); cout << "KOMPUTER : " << k;
  textcolor(15); gotoxy(53,0); cout << _pl("CZŁOWIEK : ") << c;
  fillrect(219,0x80,1,1,39,1);
  fillrect(219,0x80,1,48,39,48);
  fillrect(219,0x80,1,2,1,48);
  fillrect(219,0x8f,40,1,78,1);
  fillrect(219,0x8f,40,48,78,48);
  fillrect(219,0x8f,78,2,78,47);
}

// Procedura wyświetla strzałkę domina we wskazanym
// kolorze i na podanych współrzędnych. Jednocześnie
// współrzędne te są umieszczane na stosie
//--------------------------------------------------
void glowa(int atr, int x, int y, int kierunek, stos &s)
{
   char z;
   
   switch(kierunek)
   {
     case 1 : z = 24; break;
     case 2 : z = 26; break;
     case 3 : z = 25; break;
     case 4 : z = 27; break;
   }
   putxy(z,atr,x,y); s.zapisz(x,y);
}

// Modyfikuje współrzędne gracza w
// zależności od kierunku jego ruchu
//----------------------------------
void ruch(int atr,int kierunek,int &x,int &y)
{
  putxy(219,atr,x,y);
  switch(kierunek)
  {
    case 1 : y--; break;
    case 2 : x++; break;
    case 3 : y++; break;
    case 4 : x--; break;
  }
}

// Pobiera z odpowiedniego stosu współrzędne
// i na ich pozycji umieszcza znak X
//------------------------------------------
void upadek(int atr, stos &s)
{
  int x,y;
  if(s.rozmiar())
  {
    s.czytaj(x,y); putxy('X',atr,x,y);
  }
}

// Główna pętla zdarzeń gry
//-------------------------
void graj()
{
  
  int punkty_k = 0, punkty_c = 0;
  do
  {
    plansza(punkty_k, punkty_c);           
    
    stos stos_k, stos_c;
    int wx_k = 20, wy_k = 25, wx_c = 60, wy_c = 24;
    int kierunek_k = 1 + rand() % 4, kierunek_c = 4, zasieg = 0;
    
    do
    {
      glowa(0x80,wx_k,wy_k,kierunek_k,stos_k);
      glowa(0x8f,wx_c,wy_c,kierunek_c,stos_c);
      
      delay(100);
      
      if(zasieg) zasieg--;
      else
      {
        zasieg = 1 + rand() % 10;
        kierunek_k = 1 + rand() % 4;
      }
      
      if(kbhit() && !getch())
        switch(getch())
        {
          case 72 : kierunek_c = 1; break; // strzałka w górę
          case 77 : kierunek_c = 2; break; // strzałka w prawo
          case 80 : kierunek_c = 3; break; // strzałka w dół
          case 75 : kierunek_c = 4; break; // strzałka w lewo
        }
            
// obsługa ruchu komputera
      
      ruch(0x80,kierunek_k,wx_k,wy_k);
      if(getchxy(wx_k,wy_k) != ' ')
      {
        int k[4],kp = 0;

        switch(kierunek_k)
        {
          case 1 : wy_k++; break;
          case 2 : wx_k--; break;
          case 3 : wy_k--; break;
          case 4 : wx_k++; break;          
        }
        if(getchxy(wx_k,wy_k-1) == ' ') k[kp++] = 1;
        if(getchxy(wx_k+1,wy_k) == ' ') k[kp++] = 2;
        if(getchxy(wx_k,wy_k+1) == ' ') k[kp++] = 3;
        if(getchxy(wx_k-1,wy_k) == ' ') k[kp++] = 4;
        if(kp)
        {
          zasieg = 1 + rand() % 10;
          kierunek_k = k[rand() % kp];
        }
        ruch(0x80,kierunek_k,wx_k,wy_k);
      }
      
// obsługa ruchu człowieka

      ruch(0x8f,kierunek_c,wx_c,wy_c);

    } while((getchxy(wx_k,wy_k) == ' ') && (getchxy(wx_c,wy_c) == ' '));
    
// analiza wygranej

    if((getchxy(wx_k,wy_k) != ' ') && (getchxy(wx_c,wy_c) != ' '))
    {
// remis - nikt nie wygrywa

      while(stos_k.rozmiar() + stos_c.rozmiar())
      {
        upadek(0x80,stos_k); upadek(0x8f,stos_c); delay(30);
      }
    }
    else if(getchxy(wx_k,wy_k) != ' ')
    {
// przegrywa komputer

      while(stos_k.rozmiar())
      {
        upadek(0x80,stos_k); delay(30);
      }
      punkty_c++;
    }
    else
    {
// przegrywa człowiek
        
      while(stos_c.rozmiar())
      {
        upadek(0x8f,stos_c); delay(30);
      }
      punkty_k++;
    }

    delay(200);

    if(kbhit()) while(!getch());

  } while((punkty_k < 6) && (punkty_c < 6));

  textcolor(1);  gotoxy(13,0); cout << "KOMPUTER : " << punkty_k;
  textcolor(14); gotoxy(53,0); cout << _pl("CZŁOWIEK : ") << punkty_c;  
}

// Funkcja sprawdza, czy gracz chce kontynuować rozgrywkę
//-------------------------------------------------------
bool dalsza_gra()
{
  char klawisz;
  
  textattr(0x84);
  center(24, _pl("Dla kolejnej rozgrywki naciśnij klawisz [T]."));
  while(!(klawisz = getch())) ;
  return (klawisz == 't') || (klawisz == 'T'); 
}

// Program główny
//---------------
main()
{
  _cinit();
  srand((unsigned)time(NULL)); 
  fullscreen(true);
  cursoroff();
  ekran_tytulowy();
  do
  {
    graj();
  } while(dalsza_gra());
  cursoron();
  fullscreen(false);
}
Widok okienka konsoli
w uruchomionym programie
obrazek

       Wyjaśnienie

Gra Domino pojawiła się w latach 70-tych XX wieku na automatach firmy Atari. Zasady są bardzo proste. W grze uczestniczy gracz i komputer lub dwóch graczy. Jeden ustawia czarne domina, drugi białe. Jeśli w trakcie ustawiania dojdzie do zderzenia, to kostki domina się przewracają i przeciwnik zdobywa punkt. Jeśli obaj gracze zderzą się w tym samym ruchu, obaj przegrywają i punktu nikt nie zdobywa.

obrazek

W naszej wersji model gry jest znacznie uproszczony w stosunku do oryginału. Zamiast kostek domina gracze ciągną za sobą pasy w kolorze białym (człowiek) i czarnym (komputer). Gdy nastąpi zderzenie, pas gracza zostaje zastąpiony szkieletem z literek X, który rozprzestrzenia się od miejsca zderzenia do początku pasa. Dla tego efektu wykorzystujemy w grze strukturę stosu.

Stos

Stos jest strukturą danych, która umożliwia zapis danych i odczyt danych w kolejności odwrotnej. Np. jeśli na stosie zapisaliśmy kolejno liczby 5 7 12, to odczyt da nam 12 7 5. Stos zrealizujemy w tablicy. Dodatkowo będziemy potrzebowali jeszcze jednej zmiennej ws, tzw. wskaźnika stosu, która będzie pamiętała miejsce w tablicy do zapisu danych:

Stos pusty Stos z 2 danymi Stos pełny
         
n-1    
n-2    
...    
2    
1    
0   : ws = 0
         
n-1    
n-2    
...    
2   : ws = 2
1    
0    
        : ws = n
n-1    
n-2    
...    
2    
1    
0    

Wskaźnik stosu zawsze wskazuje komórkę leżącą ponad szczytem stosu (tak się umawiamy). Zwróć uwagę, iż przy tym założeniu ws zawiera również ilość elementów umieszczonych na stosie. Jeśli ws = 0, to stos jest pusty. Z kolei ws = n (n - ilość elementów tablicy) oznacza stos pełny. W naszej grze stos zrealizujemy za pomocą klasy. Ponieważ funkcje składowe są bardzo proste, ich definicje umieścimy bezpośrednio w definicji typu klasy.

class stos
{
Klasa otrzymuje nazwę typu stos. Wg tej nazwy będą tworzone zmienne obiektowe.
  private:

    int x[SMAX], y[SMAX];// przechowuje współrzędne; 
    int ws;              // wskaźnik stosu
W sekcji prywatnej umieszczamy tablice x i y na stosy dla współrzędnych gracza oraz wskaźnik stosu. Tablice te mają rozmiary wystarczające dla naszej gry.
  public:
W sekcji publicznej definiujemy interfejs obsługi klasy.
    stos()
    {
      ws = 0;
    }
Funkcja o takiej samej nazwie jak typ klasy jest tzw. konstruktorem. Konstruktor jest zawsze automatycznie wywoływany w momencie utworzenia egzemplarza zmiennej na podstawie typu klasy. Umożliwia on wykonanie różnych czynności inicjujących. U nas zerujemy wskaźnik stosu. Zwróć uwagę, iż konstruktor nie posiada typu, w przeciwieństwie do innych funkcji składowych klasy.
    int rozmiar()
    {
      return ws;
    }
Funkcja składowa rozmiar() zwraca wartość wskaźnika stosu, a to oznacza ilość elementów aktualnie umieszczonych na stosie.
    void zapisz(int wx, int wy)
    {
      x[ws] = wx; y[ws] = wy; ws++;
    }
Funkcja składowa zapisz() umieszcza na stosie współrzędne gracza. Po zapisie danych w tablicach wskaźnik stosu jest zwiększany - znów wskazuje puste miejsce ponad szczytem stosu.
    void czytaj(int &wx, int &wy)
    {
      ws--; wx = x[ws]; wy = y[ws];
    }
};
Ostatnia funkcja składowa czytaj() pobiera ze stosu współrzędne gracza. Najpierw jest zmniejszany wskaźnik stosu, a później pobierane dane ze wskazanej przezeń pozycji w tablicach x[ ] i y[ ]. Argumenty funkcji są przekazywane przez referencję.

Główna pętla gry

void graj()
{

  int punkty_k = 0, punkty_c = 0;
Rozgrywki rozpoczynamy od wyzerowania punktów obu graczy.
  do
  {
Pierwsza pętla steruje rozgrywkami. Wykonuje się ona dotąd, aż jeden z graczy osiągnie 6 punktów. Punkt jest przyznawany za pokonanie przeciwnika.
    plansza(punkty_k, punkty_c);
Przed wejściem do pętli wewnętrznej, sterującej pojedynczą rozgrywką, wyświetlamy planszę gry. Na planszy u góry ekranu pojawiają się punkty zdobyte przez komputer i przez człowieka. Dlatego musimy je przekazać jako parametry do funkcji tworzącej planszę.
    stos stos_k, stos_c;
    int wx_k = 20, wy_k = 25, wx_c = 60, wy_c = 24;
    int kierunek_k = 1 + rand() % 4, kierunek_c = 4, zasieg = 0;
Tworzymy zmienne niezbędne w rozgrywce:
stosy komputera i człowieka zapamiętujące ich pozycje,
współrzędne komputera i człowieka,
kierunek ruchu komputera generujemy jako liczbę pseudolosową z zakresu od 1 do 4, kierunek ruchu człowieka jest inicjowany na 4 i zasięg ruchu komputera ustawiamy na 0 - spowoduje to wygenerowanie na początku pętli nowego kierunku ruchu komputera i nowego zasięgu.
    do
    {
Rozpoczynamy pętlę wewnętrzną, która obsługuje rozgrywkę. Pętla ta kończy się po uderzeniu jednego z graczy (lub obu) w przeszkodę.
      glowa(0x80,wx_k,wy_k,kierunek_k,stos_k);
      glowa(0x8f,wx_c,wy_c,kierunek_c,stos_c);
Dla każdego z graczy wywołujemy procedurę głowa, która a współrzędnych gracza wyświetla w podanym kolorze strzałkę skierowaną zgodnie z aktualnym kierunkiem ruchu. Współrzędne gracza są zapamiętywane na jego stosie.
      delay(100);
Wprowadzamy opóźnienie - jego wartość wpływa na szybkość ruchów obu graczy
      if(zasieg) zasieg--;
      else
      {
        zasieg = 1 + rand() % 10;
        kierunek_k = 1 + rand() % 4;
      }
Komputer podąża w swoim kierunku dopóki zmienna zasięg jest różna od 0. W takim przypadku zmniejsza się ją o 1 w każdym obiegu pętli. Jeśli osiągnie 0, komputer losuje nowy zasięg z zakresu od 1 do 10 oraz nowy kierunek ruchu.
      if(kbhit() && !getch())
        switch(getch())
        {
          case 72 : kierunek_c = 1; break; // strzałka w górę
          case 77 : kierunek_c = 2; break; // strzałka w prawo
          case 80 : kierunek_c = 3; break; // strzałka w dół
          case 75 : kierunek_c = 4; break; // strzałka w lewo
        }
Sprawdzamy, czy naciśnięto klawisze sterujące. Jeśli tak, to odpowiednio modyfikujemy kierunek ruchu człowieka.
      ruch(0x80,kierunek_k,wx_k,wy_k);
Wykonujemy ruch komputera. Polega to na umieszczeniu na jego współrzędnych czarnego kwadratu i odpowiedniej modyfikacji zmiennych wx_k i wy_k.
      if(getchxy(wx_k,wy_k) != ' ')
      {
        int k[4],kp = 0;

        switch(kierunek_k)
        {
          case 1 : wy_k++; break;
          case 2 : wx_k--; break;
          case 3 : wy_k--; break;
          case 4 : wx_k++; break;          
        }
        if(getchxy(wx_k,wy_k-1) == ' ') k[kp++] = 1;
        if(getchxy(wx_k+1,wy_k) == ' ') k[kp++] = 2;
        if(getchxy(wx_k,wy_k+1) == ' ') k[kp++] = 3;
        if(getchxy(wx_k-1,wy_k) == ' ') k[kp++] = 4;
        if(kp)
        {
          zasieg = 1 + rand() % 10;
          kierunek_k = k[rand() % kp];
        }
        ruch(0x80,kierunek_k,wx_k,wy_k);
      }
Komputer sprawdza, czy po wykonaniu ruchu, nie uderzył w przeszkodę. Jeśli tak, to cofa się na poprzednią pozycję. Następnie bada, które kierunki są wolne, zapisując je w ministosie k[ ]:kp. Jeśli znalazł jakieś kierunki wolne, to losuje jeden z nich i ponownie wykonuje swój ruch. Taka metoda postępowania gwarantuje, iż komputer nie uderzy w przeszkodę o ile będzie miał wolną drogę. Jednakże "inteligencja" ruchów komputera jest bardzo niska i często wykonuje on "głupie" posunięcia zamykając sobie drogę wyjścia - co w efekcie prowadzi do nieuchronnego wyczerpania przestrzeni na planszy i przegranej. Wyzwaniem dla programisty może być opracowanie lepszej strategii dla komputera. Ale to zostawiam ambitnym.
      ruch(0x8f,kierunek_c,wx_c,wy_c);
Teraz wykonujemy ruch człowieka.
    } while((getchxy(wx_k,wy_k) == ' ') && (getchxy(wx_c,wy_c) == ' '));
Jeśli żaden z graczy w nic nie uderzył, rozgrywka jest kontynuowana.
    if((getchxy(wx_k,wy_k) != ' ') && (getchxy(wx_c,wy_c) != ' '))
    {
// remis - nikt nie wygrywa

      while(stos_k.rozmiar() + stos_c.rozmiar())
      {
        upadek(0x80,stos_k); upadek(0x8f,stos_c); delay(30);
      }
    }
    else if(getchxy(wx_k,wy_k) != ' ')
    {
// przegrywa komputer

      while(stos_k.rozmiar())
      {
        upadek(0x80,stos_k); delay(30);
      }
      punkty_c++;
    }
    else
    {
// przegrywa człowiek
        
      while(stos_c.rozmiar())
      {
        upadek(0x8f,stos_c); delay(30);
      }
      punkty_k++;
    }
Po zakończeniu pętli rozgrywki sprawdzamy, który z graczy przegrał i kasujemy jego drogę po planszy umieszczając w miejsce kwadratów znaki X. Współrzędne odczytujemy ze stosu. Ponieważ są one uzyskiwane w odwrotnej kolejności, otrzymujemy efekt przewracających się kostek domina - stąd wzięła się nazwa tej gry. Odczyt współrzędnych ze stosu i wypisanie znaku X realizuje funkcja upadek(). Wywołujemy ją dotąd, aż na stosie nie będzie już żadnych współrzędnych. Na koniec modyfikujemy zmienne przechowujące punkty graczy.
    delay(200);
    if(kbhit()) while(!getch());
Na zakończenie krótkie opóźnienie oraz wyczyszczenie bufora klawiatury z przypadkowych naciśnięć klawiszy.
  } while((punkty_k < 6) && (punkty_c < 6));
Sprawdzamy warunek kontynuacji pętli głównej.
  textcolor(1);  gotoxy(13,0); cout << "KOMPUTER : " << punkty_k;
  textcolor(14); gotoxy(53,0); cout << _pl("CZŁOWIEK : ") << punkty_c;  
}
Uaktualniamy punkty graczy wyświetlane u góry planszy i kończymy funkcję gry.


   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