[CZĘŚĆ 1] STM32Butterfly2: Tetris na STM32 – wprowadzenie do mechanizmu gry
Pojawiają się tutaj nowe zmienne: nkloceknr i rkloceknr, należy je zadeklarować globalnie. Pierwsza z nich określa, jaki klocek będzie następny po bieżącym. Pozwoli nam to na wyświetlenie na ekranie nadchodzącego klocka. Następna to zmienna z losowym rodzajem klocka. Jak sprawić, żeby w zmiennej znajdowała się losowa liczba? No cóż, musimy skorzystać z pewnego uproszczenia. W każdym systemie komputerowym znajduje się generator liczb losowych. Liczby te są generowane pseudolosowo, według ustalonych schematów, np. za pomocą zmiennej, która jest zwiększana cyklicznie w jakimś przerwaniu. Losowość takiej liczby zwiększa się głównie dzięki interakcji urządzenia z użytkownikiem. Ja wykorzystałem rozwiązanie z inkrementacją zmiennej w przerwaniu timera SysTick, daje ono w przypadku prostej gry bardzo dobre rezultaty. Ponieważ uruchomiliśmy ten timer wcześniej wystarczy że dodamy procedurę cyklicznej zmiany zmiennej rkloceknr do funkcji SysTick_Handler:
rkloceknr++; //losowanie klocków if(rkloceknr>6) rkloceknr=0;
Ponieważ funkcje obsługi przerwań znajdują się w pliku stm32f10x_it.c, a zmienna rkloceknr została zadeklarowana w pliku main.c, musimy poinformować o tym fakcie kompilator dopisując na początku funkcji SysTick_Handler:
extern unsigned char rkloceknr;
Kolejny klocek pojawia się na naszej planszy i jest gotowy do ułożenia przez gracza na dnie. Jednak co, jeśli układana piramida sięgnie samej góry planszy i zbraknie miejsca na pojawienie się nowego klocka? Gra kończy się powodując większą lub mniejszą frustrację gracza.
Dodajmy zatem do funkcji KlocekNowy fragment kodu, który sprawdzi czy pojawienie się nowego klocka nie wywoła od razu kolizji. Korzystamy w tym celu z wcześniej napisanej funkcji Kolizja:
if(Kolizja(klocekx,kloceky,klocekr) == 0) //sprawdzanie czy nowy klocek //nie spowoduje kolizji { //jeżeli nie: punkty++; //zwiększ punktację }else //jeżeli nowy klocek wywołał od razy kolizję { stan_gry=GAMEOVER; //koniec gry! }
Pojawiły się tu nowe zmienne, których użycie wymaga wyjaśnień. Najoczywistsze będzie przeznaczenie zmiennej punkty. Przechowuje ona liczbę punktów zdobytych przez gracza. Jeżeli nowy klocek mieści się na planszy punktacja zwiększana jest o jeden. Natomiast zmienna stan_gry określa, w jakim, z możliwych czterech, stanie jest gra. Na początku pliku z programem dodajemy deklaracje nazw tych stanów:
#define INTRO 1 #define GRA 2 #define GAMEOVER 3 #define PAUZA 4
Po uruchomieniu gra wyświetla planszę początkową, a zmienna stan_gry ma wartość INTRO. Dopiero naciśnięcie przycisku przez użytkownika rozpoczyna właściwą grę, zmienna zmienia stan na wartość GRA, w trakcie pauzy jej wartość przyjmie wartość PAUZA, a po zakończeniu gry GAMEOVER. Oczywiście nie obędzie się bez zadeklarowania naszych nowych zmiennych jako globalnych na początku pliku main.c:
//Zmienna określa rodzaj spadającego klocka unsigned char kloceknr; //Zmienna określa rodzaj następnego klocka unsigned char nkloceknr; //Zmienna na potrzeby losowania rodzaju klocka unsigned char rkloceknr; //Zmienna określająca stan programu unsigned char stan_gry=INTRO; //Punkty unsigned int punkty;
Ułożyliśmy, zatem klocek na planszy oraz wygenerowaliśmy kolejny, gotowy do ułożenia. Jednak, aby gra miała sens musimy premiować w postaci punktów ułożenie kompletnej linii poziomej. Poza tym kompletne linie należy usuwać, aby zrobić miejsce na kolejne, spadające klocki. Potrzebujemy zatem kolejnej funkcji, która się tym zajmie.
Cała plansza to 20 linii poziomych, które po kolei należy sprawdzić. Zaczynamy więc od pętli odliczającej kolejne linie:
for(y=0;y<20;y++)
W każdej iteracji pętli musimy sprawdzić czy dana linia zawiera wszystkie segmenty, których jest 10. Czyli kolejna pętla. Ja proponuję następującą konstrukcję:
k=1; for(x=0;x<10;x++) if(plansza[x][y] == 0) k=0;
Do zmiennej p wpisujemy na początku wartość 1. Teraz w każdym przejściu pętli sprawdzany jest jeden z dziesięciu segmentów linii. Brak jednego z segmentów spowoduje zapisanie do zmiennej p wartości 0. Więc wartość 1 zmiennej p świadczy o tym, że bieżąca linia jest kompletna.
Co jeżeli trafimy na pełną linię? Musimy usunąć ją z planszy. Dokonamy tego kopiując każdą kolejną linię nad tą, którą chcemy skasować, do linii poniżej. Odliczanie linii rozpoczniemy od aktualnie sprawdzanej:
for(yy=y;yy>0;yy--)
Teraz wystarczy przenieść kolejne wartości każdej z 10 komórek z linii y-1, czyli tej wyżej:
for(x=0;x<10;x++) plansza[x][yy]=plansza[x][yy-1];
Za usuniętą linię należą się graczowi punkty. W naszej wersji gry zrealizowałem zliczanie punktów: 1 punkt za każdy ułożony klocek, 10 punktów za każdą kompletną linię która zniknie. Punkty przechowuje zmienna punkty, ponadto w zmiennej linie zliczane są ułożone linie.