Menu Zamknij

Elektroniczne kostki do gry

Cel

Nauka posługiwania się własnymi zmiennymi i funkcjami zdefiniowanymi przez użytkownika oraz wprowadzenie do bezprzewodowej komunikacji pomiędzy modułami.

Wymagania

  • dwa moduły BBC Micro Bit z akcesoriami.

Wprowadzenie

Projekt dużo prostszej elektronicznej kostki znajduje się w tutorialu na stronie zarządzania projektami makecode.microbit.org. W tym rozdziale skupimy się jednak na wykorzystaniu komunikacji radiowej do symulacji jednoczesnego rzutu dwoma kostkami.

Umiejętność wykorzystania bezprzewodowego przesyłania informacji w micro:bicie jest o tyle istotna, że dosyć często, przy projektach średniej lub dużej złożoności, może zabraknąć fizycznych wejść/wyjść. Szczególnie przy korzystaniu z zewnętrznych modułów przyłączanych do złącza stykowego, zabierana jest możliwość dołączenia czegokolwiek. Wówczas niezbędne może okazać się skorzystanie z drugiego modułu z wolnymi pinami, a wtedy urządzenia muszą w jakiś sposób wymienić między sobą informacje.

Dlatego dla micro:bita istnieje szereg funkcji usprawniających i ułatwiających realizację bezprzewodowej komunikacji. Znajdują się one w palecie komponentów Radio i Radio/more.

Elementarna kostka

Najprostsza kostka losuje liczbę z zakresu od 1 do 6 i wyświetla ją na matrycy LED. Korzystając z przerwania Input/on shake (patrz rozdział Podstawowe wejścia i wyjścia micro:bit), można napisać program nie zawierający w ogóle bloków on start i forever.

Listing 1

Do wygenerowania losowej liczby użyliśmy bloku Math/pick random zwracającego wartość całkowitą w definiowanym przez użytkownika zakresie. Na przykładzie tego prostego programu wyłania się pewna wada programowania na tak wysokim poziomie1. Powstają bowiem pytania: czy wylosowana liczba na stałe pojawi się na wyświetlaczu, czy tylko przez chwilę, jak wpłynie na wykonanie dodanie bloku forever niezawierającego i zawierającego bloki obsługi wyświetlacza? Niestety odpowiedzi na te pytania nie jesteśmy w stanie udzielić na podstawie analizy samego kodu. Musimy uciec się do przestudiowania dokumentacji technicznej lub po prostu sprawdzić. Bardzo pomocny w takich sytuacjach jest panel podglądu. Wyczerpującą porcję wsparcia technicznego dostaniemy także na stronie support.microbit.org, a wraz z coraz głębszym poznawaniem modułu będziemy z pewnością coraz częściej tam zaglądać. Odpowiedzią na postawione pytania jest natomiast stwierdzenie, że stan wyświetlacza jest buforowany do czasu zapisu innej jego zawartości lub wyczyszczenia Basic/more/clear screen.

Listing 2

Kostki takiej możemy używać zamiast zwykłej rzucanej kostki do gry, jednak ma ona jedną wadę. Po potrząśnięciu, gdy wylosowana liczba się powtarza, nie mamy pewności, czy losowanie nastąpiło, czy nie, bo być może potrząśnięcie było zbyt słabe. Dlatego wyświetlenie wylosowanej liczby powinniśmy poprzedzić jakąś formą komunikatu „uwaga losuję”, na przykład przez wygaszenie ekranu lub wyświetlenie wybranej przez nas ikony, jak na listingu 2.

Zmienne

Modyfikowanie zaprezentowanej kostki może być problematyczne, gdy będziemy chcieli dodać pewne funkcjonalności w bloku forever, dlatego przekształcimy program tak, aby blok on shake tylko zmodyfikował pewną zmienną, która w dalszym ciągu posłuży do sterowania przebiegiem bloku forever.

Rysunek 1. Ogólny widok grupy Variable i tworzenie nowej zmiennej

Bloki zawierające i operujące na zmiennych znajdują się w grupie Variables. Na początku grupa ta jest pusta, a bloki pojawiają się w niej wraz z deklaracjami użycia kolejnych zmiennych. Aby utworzyć nową zmienną należy rozwinąć grupę Variables i kliknąć Make a Variable (rysunek 1). Pojawi się wówczas okienko dialogowe umożliwiające wpisanie nazwy. W prezentowanym przykładzie, zgodnie z zasadą opisowego nazewnictwa zmienną przechowującą wynik rzutu kostką nazwiemy liczba_oczek.

Zadeklarowane w ten sposób zmienne nie są od razu widoczne w programie, ale są w nim dostępne i wszystkie mają charakter globalny.

W bloku on shake zrezygnowaliśmy z wyświetlenia wyniku na rzecz przekształcenia zmiennej liczba_oczek na wylosowaną liczbę, a samo wyświetlenie przeniesione zostało do bloku forever. Ten przykład demonstruje sytuację, w której niezbędne jest użycie bloku on start2. Przypisujemy w nim do zmiennej liczba_oczek wartość, która nie może być otrzymana w wyniku rzutu kostką, tak aby przed pierwszym losowaniem wyświetlacz mógł pozostać wygaszony. Zapewnia nam to sprawdzenie warunku if w pętli forever.

Listing 3

Ten program posiada kilka wad. Po pierwsze za każdym razem w pętli forever sprawdzamy, czy jesteśmy na początku programu. Po drugie nie zyskujemy wiele w odchudzeniu bloku on shake, bo nadal konieczne jest wyświetlanie w nim informacji o uruchomieniu losowania. Po prostu, w rozważanym przykładzie nie jest możliwe przeniesienie show icon do bloku forever, ponieważ wówczas widzielibyśmy go przed każdym show number, a nie tylko przy losowaniu. Po załadowaniu powyższego programu do micro:bita możemy zauważyć jeszcze jeden ciekawy, a niepożądany efekt. Nie wiadomo bowiem, w jakim momencie bloku forever nastąpi zdarzenie on shake. Możemy czasami zaobserwować, że po informacji o losowaniu matryca LED przez moment wyświetla starą, znajdującą się w buforze wartość, zanim w bloku forever zostanie zmienione wyświetlanie zmienne liczba_oczek.

Zmienne typu boolean

Nie dyskwalifikuje to programu w ogólności, ale postarajmy się usunąć te „usterki”. W tym celu wprowadzimy nową zmienną logiczną (typu boolean) o nazwie czy_nowy_los.

Rysunek 2. Niezgodność typów w języku Blocks

Jest to ciekawy przypadek wyjątku od reguły dopasowywania bloków kształtami i przykład nielicznej okazji do popełnienia błędu, gdyż od przypisania wartości jednego typu, dalej typ ten będzie w programie kontrolowany (rysunek 2). Widać wyraźnie na rysunku, że wartość logiczna true o ostrych zakończeniach bloku pasuje w miejsce wartości o zaokrąglonych brzegach, a zmiana jej typu w dalszej części programu nie jest możliwa.

Elementarna kostka inaczej

Zmienna czy_nowy_los modyfikowana w trakcie losowania w bloku on shake służy do informowania programu głównego o rzucie kostką. Tylko gdy nastąpi zmiana jej wartości z false na true, uruchomimy wyświetlanie wyniku i z powrotem zmienimy jej wartość na false. W ten sposób też unikamy konieczności nadawania wartości „0” zmiennej liczba_oczek w bloku on start, ale musimy zainicjalizować zmienną czy_nowy_los wartością false.

Listing 4

Sprawdzanie czy_nowy_los = true jest nadmiarowe, bo sama zmienna jest logiczna i przyjmuje wartość true lub false. Wystarczyłoby if(czy_nowy_los). Wielu programistów uznałoby konstrukcję z listingu 4 za niezbyt poprawną. W początkowej fazie nauki programowania sprawdzenie warunku jak w programie 4 wydaje się czytelniejsze, dlatego na razie będziemy je tak zapisywać, z czasem przechodząc do formy krótszej.

Otrzymaliśmy program działający identycznie jak króciutki program elementarnej kostki z jednym blokiem on shake. Może to rodzić pytanie: czy takie działanie, poza walorami dydaktycznymi związanymi z nauką posługiwania się zmiennymi różnych typów, ma też inne korzyści? Programiści klasycznych systemów wbudowanych od początku staraliby się pisać właśnie w ten „trudniejszy” sposób ze względu na minimalizowanie działań wykonywanych w trakcie obsługi przerwania. W końcowym przykładzie w trakcie przebiegu bloku on shake nie wykonujemy zadań związanych z obsługą wejścia/wyjścia (na przykład wyświetlanie wartości czy ikony), a jedynie modyfikujemy wartość dwóch zmiennych definiujących stan układu.

Funkcje

Rzeczywiste kostki do gry zazwyczaj mają na ściankach oczka, a nie liczby. Możemy zatem zamiast liczb wyświetlać zaprojektowane przez siebie ikony naśladujące wyglądem ścianki kostki. Wymaga to zamiany show number(liczba_oczek) w głównym programie na kaskadę if … else if … … else3. Takie działanie jest poprawne, ale blok show leds pozwalający na zaprojektowanie własnej ikony jest na tyle duży, że sześć takich dodatkowych bloków w kodzie może znacząco utrudnić analizę programu. Przeniesiemy więc projekty własnych ikon do sześciu bezparametrowych funkcji.

Rysunek 3. Okno dialogowe tworzenia nowej lub edycji już istniejącej funkcji

Definiowanie funkcji jest możliwe po wybraniu z palety komponentów Advanced/Functions/Make a Function. Wyświetlone zostanie okno dialogowe Edit Function (rysunek 3). Zmieniamy nazwę doSomething na dowolną własną. W przykładzie utworzyliśmy sześć funkcji o nazwach: jeden, dwa, trzy, cztery, pięć oraz sześć. Po utworzeniu funkcji w grupie Advanced/Functions/Make a Function pojawia się blok jej wywołania.

Listing 5

Nie jest to rozwiązanie bardzo eleganckie, ale pozwala zwiększyć czytelność programu głównego, bowiem można odsunąć definicje funkcji poza obserwowany obszar edycji.

Funkcje z parametrami

Funkcje w MakeCode Blocks mogą także przyjmować parametry. Można dzięki temu napisać metodę wyświetlenia kostki, w której zareaguje ona sama na wylosowaną liczbę oczek. W tym celu w czasie tworzenia funkcji (rysunek 3) musimy dodać parametr liczbowy (Add parameter/Number). Teraz można całą kaskadę warunków przenieść do tej funkcji (w naszym przypadku wyświetl_kostkę).

Listing 6

Na listingu 6 nie jest widoczna cała funkcja, ale nie przeszkadza nam to w analizie działania programu, wręcz przeciwnie. Wystarczająca dla nas jest wiedza, jak działa dana funkcja, a nie szczegóły jej implementacji.

Na uwagę zasługuje także fakt, że parametr num jest lokalny, a jego zasięg ogranicza się do ciała funkcji wyświetl_kostkę. Nie znajdziemy go zatem na liście Variables i aby z niego skorzystać, możemy go kopiować, albo wprost przeciągać z nagłówka funkcji.

Komunikacja radiowa

W wielu grach, jak na przykład w Backgammonie lub Monopoly, wykonujemy jednoczesny rzut dwoma kostkami. Do tego celu możemy także wykorzystać moduł micro:bit, przy czym najlepiej jeżeli będziemy dysponowali dwoma egzemplarzami. Możemy wówczas wykorzystując komunikację radiową dokonać tylko jednego „potrząśnięcia” jako równoczesnego rzutu dwiema kostkami zamiast wykonywać niezależne losowania na obu modułach.

Musimy na początek zdecydować się na model komunikacji: niesymetrycznej typu klient-serwer czy symetrycznej P2P. Inaczej mówiąc, w pierwszym przypadku jeden z modułów jest nadrzędny i decyduje, jak ma się zachować (kiedy losować) drugi moduł, a w drugim przypadku oba moduły są równorzędne i każdy może zdecydować o akcji losowania i powiadomić drugi moduł. Drugie rozwiązanie w przypadku micro:bita ma tę przewagę, że pozwala na napisanie tylko jednego, wspólnego dla obu modułów programu. Może być za to w niektórych, szczególnie zawierających większą liczbę urządzeń, projektach dużo bardziej wymagające dla programisty. Rozwiązanie P2P wybierzemy jako przykładowe. Pierwsze zadanie może natomiast mieć walor dydaktyczny w pracy zespołowej.

Komunikator „Tak/Nie”

Wykorzystywanie komunikacji radiowej umożliwia grupa Radio. Najpierw jednak w bloku on start należy zadeklarować chęć korzystania z tej możliwości.

Listing 7

Wybieramy blok Radio/radio set group i ustawiamy w polu z liczbą kanał z zakresu 0–255, na którym będziemy pracować. Komunikujące się ze sobą moduły muszą mieć zgodny kanał. W naszym przykładzie wybraliśmy 21.

Przekażemy teraz literę „T” lub „N”, w zależności od naciśniętego przycisku A lub B, z jednego micro:bita na drugi i wyświetlimy ją na drugim. Wykorzystamy bloki Radio/send string oraz reakcję na zdarzenie Radio/on radio received.

Listing 8


Napisany program jest „symetryczny”, umożliwia zatem komunikację obustronną. Jak widać on radio received pozwala na odbiór różnego typu informacji, w tym całych napisów.

Rysunek 4. Symulacja działania komunikatora. Na module po lewej stronie naciśnięto przycisk B, a po prawej przycisk A

Na symulowanym w panelu podglądu działaniu programu (na rysunku 4 w widoku pełnoekranowym), po wysłaniu komunikatu, automatycznie pojawi się drugi moduł. Można docenić, w tej sytuacji, znaczenie kolorystyki urządzeń.

Gdyby modułów działających na tym samym kanale było więcej, to nadawanie odbywałoby się na zasadzie broadcastingu i tę samą informację mogłyby odebrać wszystkie urządzenia.

Dwie kostki

Możemy teraz poskładać wszystko w jeden projekt.

Listing 9

W widoku programu pominięto definicję funkcji wyświetl_kostkę, która jest taka sama jak dla pojedynczej kostki. Dodano natomiast w bloku on start blok radio set group, co pozwala na korzystanie z komunikacji radiowej. W bloku on shake dodano także wysłanie komunikatu do drugiego urządzenia w postaci liczby „0” informującego, że nastąpił rzut kostkami. Wartość liczby w komunikacie jest bez znaczenia, bo drugi moduł reaguje na odebranie liczby niezależnie od jej wartości i nigdzie jej nie wykorzystuje. Na odebranie komunikatu radiowego moduł musi zareagować tak samo jak na potrząśnięcie, z tą różnicą, że nie odsyła radiowo żadnej wiadomości, stąd podobieństwo zawartości bloków on shake i on radio received. Dodatkowo program jest o tyle uniwersalny, że działa dla dowolnej liczby kostek, w tym także pojedynczej.

Modyfikacje

Rysunek 5. Dwie kostki w działaniu

Istnieje możliwość jeszcze większego odchudzenia bloków obsługujących przerwania. Można tylko stwierdzić, że losowanie nastąpiło, a samego aktu losowania dokonać w bloku forever.

Niewielkim nakładem pracy można zmodyfikować program w taki sposób, aby naśladował zachowanie kostek używanych w grze Farmer opracowanej przez polskiego matematyka Karola Borsuka podczas niemieckiej okupacji Warszawy w czasie II wojny światowej. Dwie kostki do Farmera mają kształt dwunastościanów foremnych, a na ściankach symbole zwierząt: królików, owiec, świń, krowy, konia, wilka i lisa. Do zadań opracowania takich kostek należałoby zaprojektowanie ikon jednoznacznie reprezentujących zwierzęta oraz losowanie zgodne z zasadami prawdopodobieństwa, bowiem obie kostki są różne i różna jest liczba zwierząt na kostkach. Niemożliwe jest zatem, w takim przypadku, napisanie programu symetrycznego.

Innym zadaniem jest napisanie programów dla trzech micro:bitów, dla których jeden jest nadrzędny i ten właśnie decyduje, kiedy następuje losowanie, a dwa pozostałe tylko wyświetlają wyniki rzutu. Złożoność zadania jest uzależniona od roli odbiorników. Wychodząc od programu dla dwóch kostek, wystarczy że nadajnik wyśle jakąkolwiek liczbę, a oba odbiorniki dokonają losowania (w odpowiedzi na zdarzenie odbioru liczby) i wyświetlenia wyniku. Znacznie trudniejszym zadaniem jest losowanie obu liczb na nadajniku, kiedy odbiorniki są tylko wyświetlaczami wyniku. Trzeba wtedy bowiem, nie tylko przesłać dwie liczby, lecz także muszą one trafić do odpowiedniego modułu. Można to zrobić na wiele sposobów, na przykład:

  • przez zastosowanie nazwanych zmiennych, co jest możliwe drogą radiową w micro:bicie,
  • kodując informację przez wysyłanie liczb z innych zakresów (nie tylko od 1 do 6),
  • kodując informacje w inny sposób,
  • wysyłając dodatkowe dane informujące moduły, który z nich ma ignorować dane,
  • używając bloku Radio/more/radio set transmit serial number dołączającego numer seryjny modułu do wszystkich nadawanych danych,
  • przez korzystanie z różnych kanałów.

Literatura

Powtórzenie zaprezentowanych projektów nie wymaga studiowania dodatkowej literatury. Dopiero wprowadzenie modyfikacji może wiązać się z koniecznością uzupełnienia wiedzy. Najlepszym źródłem informacji w przypadku jakiegoś konkretnego problemu jest internet. Książka pisana swoją wyższość ukazuje dopiero jako podręcznik czy przewodnik.

Informacje bibliograficzne dotyczące źródeł internetowych zamieściłem bezpośrednio w tekście, przy zagadnieniach, których dotyczą.

Do zapisu linków, tylko w tym spisie, wykorzystywana jest metoda skracania adresów (URL shortening). W tym skrypcie wybrano serwis TinyURL. Dostęp do wszystkich internetowych zasobów został przetestowany 27.02.2020.

Bibliografia zawiera tylko materiały pomocnicze do projektów, których dotyczą, a pominięto literaturę o charakterze ogólnoinformatycznym. Dobór materiałów do nauki kodowania, czy obsługi programów narzędziowych, pozostaje po stronie nauczyciela.

Literatury dotyczącej micro:bita jest niewiele, szczególnie w stosunku do najbardziej popularnych systemów wbudowanych. Nie można liczyć niestety na wiele informacji w języku polskim. Najbardziej godne polecenia, gromadzące także dużo społeczności są strony oficjalnego wsparcia:

Literatura uzupełniająca

W niektórych projektach nie wszystko da się podłączyć za pomocą pasujących do siebie wtyków i złącz. Należy wówczas wykonać takie podłączenia samodzielnie. Wymaga to nieco umiejętności majsterkowania i elektroniki. Bardzo ciekawą książką dla początkujących majsterkowiczów jest:

  • Roberts Dustyn, Wpraw to w ruch. Proste mechanizmy dla wynalazców, majsterkowiczów i artystów [e-book], tłum. Krzysztof Sawka, Helion, 2015.

Znajdziemy w niej oprócz podstaw elektroniki także dużo elementarnej mechaniki i materiałoznawstwa.

Majsterkowanie z użyciem systemów wbudowanych, tam gdzie nie wszystko pasuje do siebie za pomocą dedykowanych styków, wymaga elementarnej wiedzy z elektroniki. Niezmiennie najważniejszą książką na półce elektronika jest:

  • Horowitz Paul, Hill Winfield, Sztuka elektroniki, tłum. Bogusław Kalinowski, Grażyna Kalinowska, t. 1–2, wyd. 12 zmienione, WKŁ, Warszawa 2018.

Jednakże jest to pozycja dla wymagającego czytelnika. Podobnym standardem, ale skierowanym do początkujących jest:

  • Platt Charles, Elektronika. Od praktyki do teorii. Wydanie II [e-book], tłum. Konrad Matuk, Helion, 2016.

Na uwagę zasługuje ponadto, w moim przekonaniu bardzo dobra, książka polskiego autora:

  • Górecki Piotr, Wyprawy w świat elektroniki, t. 1, WKŁ, Warszawa 2006.
  • Górecki Piotr, Wyprawy w świat elektroniki. Wyższy stopień wtajemniczenia, t. 2, WKŁ, Warszawa 2011.

1 Poziom języka w programowaniu nie odnosi się do jego zaawansowania lub profesjonalizmu programisty, ale jest pojęciem wyrażającym ilość elementarnego kodu kryjącego się za pojedynczymi instrukcjami języka.

2 Ten przykład jest o tyle niefortunny, że w języku Blocks zadeklarowane zmienne są automatycznie inicjalizowane liczbą całkowitą „0”. Zatem pominięcie „set” nie zmieniłoby działania programu. Jednak dobra praktyka programistyczna nakazuje nieużywanie niezdefiniowanych zmiennych.

3 W MakeCode Blocks nie ma instrukcji wyboru case.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *