LinkedIn YouTube Facebook
Szukaj

Newsletter

Proszę czekać.

Dziękujemy za zgłoszenie!

Wstecz
Artykuły

[CZĘŚĆ 2] STM32Butterfly2: Tetris na STM32 – interfejs użytkownika

Do rysowania pojedynczych pikseli służy funkcja LcdPixel. Przyjmuje ona trzy argumenty: współrzędną X, współrzędną Y oraz wartość PIXEL_ON lub PIXEL_OFF w zależności czy zapalamy czy gasimy dany piksel. Przy obliczaniu współrzędnych korzystamy z zmiennych x i y z pętli. Mnożymy te wartości przez 2, ponieważ każdy segment planszy ma wymiar 2×2, oraz dodajemy wcześniej zdefiniowany offset oraz ewentualne przesunięcie o 1 w obrębie danego segmentu. Analogicznie narysujemy spadający klocek, jednak tym razem użyjemy jednej pętli odliczającej 4 kolejne jego segmenty:

  //rysownie spadającego klocka
  for(i=0;i<4;i++)				//odliczanie 4 segmentów klocka
  {
    x=klocki[kloceknr][klocekr][i][0] + klocekx-2;	//obliczanie współrzędnych na planszy 
    y=klocki[kloceknr][klocekr][i][1] + kloceky-2; //danego segmentu klocka
    LcdPixel(POFFX+x*2, POFFY+y*2, PIXEL_ON);	//rysowanie kolejnych 4 pikseli segmentu
    LcdPixel(POFFX+x*2+1, POFFY+y*2, PIXEL_ON);
    LcdPixel(POFFX+x*2, POFFY+y*2+1, PIXEL_ON);
    LcdPixel(POFFX+x*2+1, POFFY+y*2+1, PIXEL_ON);
   }

Tym razem, aby obliczyć współrzędne danego segmentu musimy sięgnąć do tablicy definiującej klocek podając numer kształtu klocka oraz jak jest obrócony. Następnie dodać offset planszy oraz współrzędną klocka na planszy. Ponieważ współrzędne klocka umownie wskazują jego środek odejmujemy jeszcze od wszystkiego wartość 2. Tak obliczoną wartość x i y wykorzystujemy do narysowania czterech pikseli danego segmentu klocka.
Identycznie narysujemy drugi klocek w okienku z podpisem NEXT. Jest to podpowiedź dla gracza, jakiego następnego klocka ma się spodziewać. Zmiany będą polegały na wykorzystaniu innych offsetów oraz zmiennej nkloceknr zamiast kloceknr.
Zostało nam jeszcze wyświetlenie wyniku gry. Typowe biblioteki graficzne oferują nam zazwyczaj funkcję wyświetlającą pojedyncze znaki ASCII. Ja mam w swojej bibliotece właśnie taką funkcję: LcdChr(znak_ascii) oraz funkcję LcdGotoXY(wsp.x, wsp.y) do ustawiania kursora na daną pozycję. Przy czym rysowanie kolejnych znaków automatycznie przesuwa kursor do przodu.
Identyczny fragment programu wyświetla liczbę zaliczonych linii. Ponieważ wszystkie dotychczasowe operacje graficzne były dokonywane na buforze, funkcja wyświetlająca kończy się wywołaniem funkcji LcdUpdate, która przenosi zawartość bufora na ekran.
W tym miejscu na pewno każdy nie myśli o niczym innym, jak tylko o tym, aby w końcu wypróbować to, co do tej pory napisaliśmy. Skompilowanie i uruchomienie programu z niemal pustą funkcją main na pewno jest bezcelowe. Musimy dodać główną pętlę programu, w której zawartość ekranu będzie cyklicznie odświeżana w zależności od wartości zmiennej stan_gry. Proponuję dodać do funkcji main następującą procedurę:

  while (1)
  {
    if(refresh==1)
    {
      refresh=0;
      switch(stan_gry) //sprawdzanie stanu gry
      {
      case GRA: //gra uruchomiona
        RysujEkran();
        break;

      case GAMEOVER: //koniec gry
        break;

      case PAUZA: //pauza
        break;

      case INTRO: //intro
      	break;
      }
    }
  }

W nieskończonej pętli sprawdzana jest wartość nowej zmiennej refresh. Pozwoli to dać sygnał z innych części programu, kiedy należy odświeżyć zawartość ekranu i zapobiegnie wykonywaniu tej czynności w kółko nawet wtedy, gdy nie jest to konieczne. Aby wygodnie było operować zmienną refresh dodałem prostą funkcję (poniżej), która ją ustawia:

//Funkcja ustawia zmienną refresh
void OdswiezEkran(void)
{
	refresh=1;
}

W konstrukcji opartej na switch program sprawdza wartość zmiennej stan_gry, ponieważ nie zawsze zawartość wyświetlacza będzie taka sama. Na razie program zadziała tylko w czasie rozgrywki, wyświetlając planszę gry. Z czasem dodamy funkcję wyświetlającą intro oraz komunikaty o pauzie i zakończeniu gry. Podobną konstrukcję opartą o polecenie switch dodamy do obsługi klawisza „ok” (naciśnięcie z góry) joysticka:

    switch(stan_gry)
    {
      case GRA:                 //Jeżeli gra włączona
        stan_gry=PAUZA;         //włącz pauzę
      break;

      case PAUZA:               //Jeżeli gra w czasie pauzy
        stan_gry=GRA;        	   //wróć do gry
	    break;

      case INTRO:               //Jeżeli włączone intro
        stan_gry=GRA;
        break;

      case GAMEOVER:            //Jeżeli nastąpiła przegrana
        CzyscPlansze();         //wyczyść planszę
        KlocekNowy();           //zmień klocek na nowy
        punkty=0;               //wyzeruj licznik punktów
        linie=0;                //i zaliczonych linii

        stan_gry=INTRO;         //przejdź do intro
        break;
    }
    OdswiezEkran();

Jeżeli naciśniemy „ok” w trakcie trwania intro, to uruchomi się gra, w czasie gry uruchomimy w ten sposób pauzę, a po zakończeniu gry powrócimy do intro uprzednio kasując punkty i czyszcząc planszę.
Na końcu uruchamiana jest funkcja OdswiezEkran, aby wszystkie zmiany widoczne były natychmiast na ekranie. Jej wywołanie musimy dodać na końcu każdego fragmentu obsługującego poszczególne ruchy joysticka.
Pozostało nam już tylko upiększyć naszą grę, dodać jakąś ładną stronę tytułową i napisy świadczące o wstrzymaniu (pauzie) i zakończeniu gry. Zacznijmy od najprostszego napisu PAUZA, jaki pojawi się podczas przerwy w grze. Okazało się, że dobrze wygląda napis PAUZA nałożony na dotychczasowy ekran z grą. Jeżeli naciśniemy „ok” joysticka to napis zniknie, a gra będzie toczyła się dalej. Spróbujmy dodać ten efekt do gry. Najpierw zaprojektowałem wygląd tekstu informującego o wstrzymaniu gry, który będzie wyświetlany na tle będącym zawartością ekranu.
Jak to zrobić? Tego, co mamy już na wyświetlaczu nie musimy oczywiście odczytywać. Całą zawartość ekranu mamy w buforze, dalej wykorzystamy funkcję, której użyliśmy do wyświetlenia tła (kopiuje 504 bajty z tablicy do bufora ekranu). Można ją łatwo przerobić tak, żeby nakładała grafikę z tablicy na dotychczasową zawartość ekranu. Wykorzystałem do tego funkcję logiczną OR (LcdCache[i] = LcdCache[i]|bitmap[i];) i stworzyłem nową funkcje o nazwie LcdLoadAddBMP, która „dodaje” nową grafikę na tło. Teraz wystarczy zamienić projekt grafiki napisu PAUSE na tablicę dokładnie tak samo jak w przypadku tła i wyświetlić ja przy pomocy nowej funkcji.
Naciśnięcie przycisku „ok” uruchomi pauzę, zmieniając zmienną stangry. Dzieje się to w obsłudze przerwania wywoływanego przez joystick, która kończy się wywołaniem funkcji OdswiezEkran. W konsekwencji zostanie wykonana procedura w funkcji main, w której znajduje się wcześniej przygotowana przez nas struktura case. Właśnie tam musimy umieścić polecenia rysujące napis PAUSE. Żeby napis był czytelny trzeba go otoczyć wygaszonymi pikselami. Czeka nas dodanie kolejnej funkcji graficznej, tym razem „odejmującej”. Przygotowujemy grafikę tła dla naszego napisu. Z efektu naszej pracy generujemy tablicę z danymi, tym razem nazwaną pauza2. Funkcja odejmująca wygląda tak:

void LcdLoadSubBMP (const unsigned char* bitmap)
{
  int i;

  for ( i = 0; i < LCD_CACHE_SIZE; i++ )
  {
    LcdCache[i] = LcdCache[i]&( 0xFF-bitmap[i]);
  }
}
Polski portal branżowy dedykowany zagadnieniom elektroniki. Przeznaczony jest dla inżynierów i konstruktorów, projektantów hardware i programistów oraz dla studentów uczelni technicznych i miłośników elektroniki. Zaglądają tu właściciele startupów, dyrektorzy działów R&D, zarządzający średniego szczebla i prezesi dużych przedsiębiorstw. Oprócz artykułów technicznych, czytelnik znajdzie tu porady i pełne kursy przedmiotowe, informacje o trendach w elektronice, a także oferty pracy. Przeczyta wywiady, przejrzy aktualności z branży w kraju i na świecie oraz zadeklaruje swój udział w wydarzeniach, szkoleniach i konferencjach. Mikrokontroler.pl pełni również rolę patrona medialnego imprez targowych, konkursów, hackathonów i seminariów. Zapraszamy do współpracy!