Gra saper na LCDku - jak napisac od podstaw
: sobota 05 sie 2017, 19:26
Witam wszystkich
Dziś mnie natchnęło na zrobienie prostej gierki – sapera, ale nie o samą grę tu chodzi, a o to, ze przy okazji przedstawię krok po kroku z przykładami jak taką grę napisać pokazując swoją metodę podchodzenia do czegoś takiego.
Założenia projektu:
- kod na tyle „uniwersalny”, żeby można było go dostosować do innych rzeczy
- wynikiem ma być w pełni funkcjonalna gra
- wyświetlanie pozostałej ilości bomb
- randomowe plansze – to w późniejszej wersji – wjedzie pewnie szum z ADC jako mechanizm generowania liczb pseudolosowych (czy wtedy już będą losowe?)
Jest to dość spory projekt – potrzebujemy więc plan działania co i jak.
1. wybór platformy sprzętowej
2. pierwotny wygląd planszy – funkcja rysująca
3. funkcje umożliwiające „zapełnienie” poszczególnej kratki jaką chcemy
4. funkcje umożliwiające przechodzenie pomiędzy poszczególnymi kratkami
5. sposób zapisu danych odnośnie planszy + funkcje
6. sposoby wyświetlania danych na planszy + funkcje
7. interakcje - czyli ogólnie gra - zaznaczanie pól, odkrywanie, wygrana/przegrana + funkcje
7. sposób przedstawienia wyniku/ilości pozostałych bomb itp. + funkcje
Roboty jest sporo więc zaczynamy.
1. wybór platformy sprzętowej
Tutaj w moim przypadku najlepszym wyborem jest płytka z projektu SUDOKU STM32 – zawiera mikrokontrolerek STM32F030C8T6, kolorowy wyświetlacz ILI9341 + przyciski. Dzięki temu też podstawowe funkcje miałem już napisane – teraz je poprzerabiałem na bardziej uniwersalne – więc nawet jakby ktoś chciał zrobić szachy na tej podstawie – nie ma problemu!
2. Pierwotny wygląd planszy – funkcja rysująca
Wszyscy zapewne wiemy jak wygląda saper. Plansza z kwadratami, jednak jej rozmiar nie jest do końca zdefiniowany. Pamiętając o założeniu z uniwersalnością najpierw zadeklarujmy sobie kilka rzeczy z których najważniejsze to ile pikseli ma mieć bok kratki, ile kratek ma być w osi X i ile w osi Y. Czyli prosty sposób:
Możemy teraz napisać funkcję, która rysuje obramowania:
Efekt widzimy na zdjęciu:
Efekt dla ustawień:
Jest taki:
Czyli ten punkt już ogarnięty!
3. funkcje umożliwiające „zapełnienie” poszczególnej kratki jaką chcemy
Co chcemy uzyskać – funkcje, która po podaniu numeru kratki i zawartości – zadba nam o jej wypełnienie. Dla ułatwienia wpiszmy teraz do niej jakąś cyferkę.
Wjeżdzaja kolejne definy – potrzebne do umiejscowienia liteki w kratce – czyli przesunięcie w osi X i Y od piksela z „początkiem” kratki:
Najwygodniej będzie, jeśli nasza funkja będzie przyjmować numer kratki, jej zawartość i kolor tła. Wygląda ona następująco:
Komentarze z kodu mówią same za siebie.
Przetestujmy teraz jej działanie za pomocą funkcji testowej:
Efekt – na filmiku:
https://youtu.be/M6DcSE_EoiE
Jak widać – wszystko działa ok! nawet dla różnych rozmiarów planszy.
4. funkcje umożliwiające przechodzenie pomiędzy poszczególnymi kratkami
Co chcemy uzyskać – możliwość przechodzenia pomiędzy kolejnymi kratkami za pomocą „strzałek na płytce”. Wiadomo strzałka w górę – do góry itp.
Wprowadźmy kilka zmiennych globalnych (globalnych dla wygody):
Zróbmy jedną funkcję ruchu, która jako parametr pobierze kierunek ruchu. Ułatwmy sobie sprawę enumem:
enum{ruch_lewo, ruch_prawo, ruch_gora,ruch_dol};
I tak wygląda nasza funkcja:
Teraz kwestia pożenienia tego ze sprzętem – akurat ja stosuję bibliotekę do obsługi przycisków, która wywołuje podpięte callbacki do eventów przycisków. Musimy więc dla każdego przycisku napisać osobną funkcję:
I podpiąć je do przycisków:
Wyżej użyliśmy funkcji “zamaluj_pola_po_ruchu();” – co ona robi – dla pozycji ze zmiennej „wybrana_kostka” wpisuje cyferkę „1”, natomiast dla poprzedniej wpisuje cyferkę „0” – wtedy ładnie przetestujemy działanie.
Tak ona wygląda:
Uruchamiamy całość i oto efekt:
https://youtu.be/S8M40DbdZ1Y
W sumie mamy już nasz „silnik gry” dalej skupimy się bardziej na logice gry.
CDN... komentarze uwagi itp mile widziane
kody udostępnie na samym końcu
Dziś mnie natchnęło na zrobienie prostej gierki – sapera, ale nie o samą grę tu chodzi, a o to, ze przy okazji przedstawię krok po kroku z przykładami jak taką grę napisać pokazując swoją metodę podchodzenia do czegoś takiego.
Założenia projektu:
- kod na tyle „uniwersalny”, żeby można było go dostosować do innych rzeczy
- wynikiem ma być w pełni funkcjonalna gra
- wyświetlanie pozostałej ilości bomb
- randomowe plansze – to w późniejszej wersji – wjedzie pewnie szum z ADC jako mechanizm generowania liczb pseudolosowych (czy wtedy już będą losowe?)
Jest to dość spory projekt – potrzebujemy więc plan działania co i jak.
1. wybór platformy sprzętowej
2. pierwotny wygląd planszy – funkcja rysująca
3. funkcje umożliwiające „zapełnienie” poszczególnej kratki jaką chcemy
4. funkcje umożliwiające przechodzenie pomiędzy poszczególnymi kratkami
5. sposób zapisu danych odnośnie planszy + funkcje
6. sposoby wyświetlania danych na planszy + funkcje
7. interakcje - czyli ogólnie gra - zaznaczanie pól, odkrywanie, wygrana/przegrana + funkcje
7. sposób przedstawienia wyniku/ilości pozostałych bomb itp. + funkcje
Roboty jest sporo więc zaczynamy.
1. wybór platformy sprzętowej
Tutaj w moim przypadku najlepszym wyborem jest płytka z projektu SUDOKU STM32 – zawiera mikrokontrolerek STM32F030C8T6, kolorowy wyświetlacz ILI9341 + przyciski. Dzięki temu też podstawowe funkcje miałem już napisane – teraz je poprzerabiałem na bardziej uniwersalne – więc nawet jakby ktoś chciał zrobić szachy na tej podstawie – nie ma problemu!
2. Pierwotny wygląd planszy – funkcja rysująca
Wszyscy zapewne wiemy jak wygląda saper. Plansza z kwadratami, jednak jej rozmiar nie jest do końca zdefiniowany. Pamiętając o założeniu z uniwersalnością najpierw zadeklarujmy sobie kilka rzeczy z których najważniejsze to ile pikseli ma mieć bok kratki, ile kratek ma być w osi X i ile w osi Y. Czyli prosty sposób:
Kod: Zaznacz cały
#define BOK_KRATKI 25
#define PLANSZA_X 9
#define PLANSZA_Y 11
#define PLANSZA_START_X 6
#define PLANSZA_START_Y 6
#define PLANSZA_GRUBOSC_LINII 3
#define KOLOR_OBRAMOWANIA ILI9341_BLUE
Możemy teraz napisać funkcję, która rysuje obramowania:
Kod: Zaznacz cały
// funkcja rysujaca obramowanie gry
void rysuj_obramowanie(void)
{
// linie poziome
for(uint8_t i = 0; i < PLANSZA_Y + 1; i++)
{
lcd_zapelnij_prostokat( PLANSZA_START_X,
PLANSZA_START_X + i * BOK_KRATKI,
BOK_KRATKI * PLANSZA_X + PLANSZA_GRUBOSC_LINII,
PLANSZA_GRUBOSC_LINII,
KOLOR_OBRAMOWANIA);
}
// linie pionowe
for(uint8_t i = 0; i < PLANSZA_X + 1; i++)
{
lcd_zapelnij_prostokat( PLANSZA_START_X + i * BOK_KRATKI,
PLANSZA_START_Y,
PLANSZA_GRUBOSC_LINII,
BOK_KRATKI * PLANSZA_Y + PLANSZA_GRUBOSC_LINII,
KOLOR_OBRAMOWANIA);
}
}
Efekt widzimy na zdjęciu:
Efekt dla ustawień:
Kod: Zaznacz cały
#define PLANSZA_X 6
#define PLANSZA_Y 6
#define PLANSZA_START_X 30
#define PLANSZA_START_Y 30
Jest taki:
Czyli ten punkt już ogarnięty!
3. funkcje umożliwiające „zapełnienie” poszczególnej kratki jaką chcemy
Co chcemy uzyskać – funkcje, która po podaniu numeru kratki i zawartości – zadba nam o jej wypełnienie. Dla ułatwienia wpiszmy teraz do niej jakąś cyferkę.
Wjeżdzaja kolejne definy – potrzebne do umiejscowienia liteki w kratce – czyli przesunięcie w osi X i Y od piksela z „początkiem” kratki:
Kod: Zaznacz cały
#define PRZESUNIECIE_TEKSTU_X 4
#define PRZESUNIECIE_TEKSTU_Y 4
Najwygodniej będzie, jeśli nasza funkja będzie przyjmować numer kratki, jej zawartość i kolor tła. Wygląda ona następująco:
Kod: Zaznacz cały
// funkcja wpisujaca w podana kratke okreslona liczbe
void zapelnij_kratke(uint8_t numer, uint8_t liczba,uint16_t kolor)
{
char buff[2] = "a";
buff[0] = liczba + 48;
// zamalowanie poprzedniej zawartosci kratki
lcd_zapelnij_prostokat( PLANSZA_START_X + PLANSZA_GRUBOSC_LINII + (numer%PLANSZA_X) * BOK_KRATKI,
PLANSZA_START_Y + PLANSZA_GRUBOSC_LINII + (numer/PLANSZA_X) * BOK_KRATKI,
BOK_KRATKI - PLANSZA_GRUBOSC_LINII,
BOK_KRATKI - PLANSZA_GRUBOSC_LINII,
kolor);
// wypelnienie literka
lcd_pisz_tekst_16( PLANSZA_START_X + PLANSZA_GRUBOSC_LINII + (numer%PLANSZA_X) * BOK_KRATKI + PRZESUNIECIE_TEKSTU_X,
PLANSZA_START_Y + PLANSZA_GRUBOSC_LINII + (numer/PLANSZA_X) * BOK_KRATKI + PRZESUNIECIE_TEKSTU_Y,
buff,
ILI9341_BLACK,
kolor);
}
Komentarze z kodu mówią same za siebie.
Przetestujmy teraz jej działanie za pomocą funkcji testowej:
Kod: Zaznacz cały
// funkcja testowa zapelniajaca kolejne komorki planszy
void test_zapelnienia()
{
uint8_t liczba = 0;
uint8_t kratka = 0;
while(1)
{
zapelnij_kratke(kratka,liczba,ILI9341_CYAN);
liczba++;
kratka++;
if( liczba == 10 )
{
liczba = 0;
}
if(kratka == PLANSZA_X * PLANSZA_Y)
{
kratka = 0;
}
_delay_ms(200);
}
}
Efekt – na filmiku:
https://youtu.be/M6DcSE_EoiE
Jak widać – wszystko działa ok! nawet dla różnych rozmiarów planszy.
4. funkcje umożliwiające przechodzenie pomiędzy poszczególnymi kratkami
Co chcemy uzyskać – możliwość przechodzenia pomiędzy kolejnymi kratkami za pomocą „strzałek na płytce”. Wiadomo strzałka w górę – do góry itp.
Wprowadźmy kilka zmiennych globalnych (globalnych dla wygody):
Kod: Zaznacz cały
// numer wybranej kostki - pozycja "kursora" na planszy - tam gdzie aktualnie się znajdujemy
uint8_t wybrana_kostka = 0;
// numer kostki z ktorej zrobilismy ruch
uint8_t poprzednia_kostka;
Zróbmy jedną funkcję ruchu, która jako parametr pobierze kierunek ruchu. Ułatwmy sobie sprawę enumem:
enum{ruch_lewo, ruch_prawo, ruch_gora,ruch_dol};
I tak wygląda nasza funkcja:
Kod: Zaznacz cały
// funkcja wyliczajaca nastepna pozycje kratki
void ruch_po_planszy( uint8_t kierunek )
{
switch( kierunek )
{
case ruch_lewo:
{
poprzednia_kostka = wybrana_kostka;
wybrana_kostka--;
if(wybrana_kostka == 255)
{
wybrana_kostka = ( PLANSZA_X * PLANSZA_Y ) - 1;
}
break;
}
case ruch_prawo:
{
poprzednia_kostka = wybrana_kostka;
wybrana_kostka++;
if(wybrana_kostka == ( PLANSZA_X * PLANSZA_Y ))
{
wybrana_kostka = 0;
}
break;
}
case ruch_gora:
{
poprzednia_kostka = wybrana_kostka;
wybrana_kostka -= PLANSZA_X;
if(wybrana_kostka > 200)
{
wybrana_kostka = poprzednia_kostka + ( PLANSZA_X * ( PLANSZA_Y - 1 ) );
}
break;
}
case ruch_dol:
{
poprzednia_kostka = wybrana_kostka;
wybrana_kostka += PLANSZA_X;
if(wybrana_kostka > ( PLANSZA_X * PLANSZA_Y ) - 1)
{
wybrana_kostka = wybrana_kostka%PLANSZA_X;
}
break;
}
}
}
Teraz kwestia pożenienia tego ze sprzętem – akurat ja stosuję bibliotekę do obsługi przycisków, która wywołuje podpięte callbacki do eventów przycisków. Musimy więc dla każdego przycisku napisać osobną funkcję:
Kod: Zaznacz cały
void button_left()
{
ruch_po_planszy( ruch_lewo );
zamaluj_pola_po_ruchu();
}
void button_right()
{
ruch_po_planszy( ruch_prawo );
zamaluj_pola_po_ruchu();
}
void button_up()
{
ruch_po_planszy( ruch_gora );
zamaluj_pola_po_ruchu();
}
void button_down()
{
ruch_po_planszy( ruch_dol );
zamaluj_pola_po_ruchu();
}
I podpiąć je do przycisków:
Kod: Zaznacz cały
// przypisanie funkcji do przyciskow
button_ustaw(0,5,200,50,button_up, NULL );
button_ustaw(1,5,200,50,button_left, NULL );
button_ustaw(2,5,200,50,button_ok, NULL );
button_ustaw(3,5,200,50,button_right,NULL );
button_ustaw(4,5,200,50,button_down, NULL );
Wyżej użyliśmy funkcji “zamaluj_pola_po_ruchu();” – co ona robi – dla pozycji ze zmiennej „wybrana_kostka” wpisuje cyferkę „1”, natomiast dla poprzedniej wpisuje cyferkę „0” – wtedy ładnie przetestujemy działanie.
Tak ona wygląda:
Kod: Zaznacz cały
void zamaluj_pola_po_ruchu()
{
zapelnij_kratke(poprzednia_kostka,0,ILI9341_WHITE);
zapelnij_kratke(wybrana_kostka,1,ILI9341_YELLOW);
}
Uruchamiamy całość i oto efekt:
https://youtu.be/S8M40DbdZ1Y
W sumie mamy już nasz „silnik gry” dalej skupimy się bardziej na logice gry.
CDN... komentarze uwagi itp mile widziane