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