Spisu treści:

Rywalizacja sieciowa: gra o niskiej latencji dla BBC Micro:bit: 10 kroków (ze zdjęciami)
Rywalizacja sieciowa: gra o niskiej latencji dla BBC Micro:bit: 10 kroków (ze zdjęciami)

Wideo: Rywalizacja sieciowa: gra o niskiej latencji dla BBC Micro:bit: 10 kroków (ze zdjęciami)

Wideo: Rywalizacja sieciowa: gra o niskiej latencji dla BBC Micro:bit: 10 kroków (ze zdjęciami)
Wideo: Uzależnienie od gier rankingowych - jak może zniszczyć Ci życie? 2024, Listopad
Anonim
Rywalizacja sieciowa: gra o niskim opóźnieniu dla BBC Micro:bit
Rywalizacja sieciowa: gra o niskim opóźnieniu dla BBC Micro:bit
Rywalizacja sieciowa: gra o niskim opóźnieniu dla BBC Micro:bit
Rywalizacja sieciowa: gra o niskim opóźnieniu dla BBC Micro:bit

W tym samouczku wyjaśnię, jak zaimplementować podstawową grę wieloosobową na BBC micro:bit z następującymi funkcjami:

  • Prosty interfejs
  • Niskie opóźnienie między naciśnięciami przycisków a aktualizacjami ekranu
  • Elastyczna liczba uczestników
  • Łatwa kontrola nad grą za pomocą głównego urządzenia zdalnego („root”)

Gra jest zasadniczo symulacją polityki. Wszyscy gracze rozpoczynają grę nieprzypisani do żadnej drużyny, z wyjątkiem dwóch graczy. Jeden z tych graczy jest przypisany do Drużyny A, a drugi do Drużyny B.

Celem gry jest to, aby każdy gracz był w drużynie z większością graczy w momencie, gdy wszyscy są nawróceni.

Powyższy schemat ilustruje maszynę skończoną, czyli specyfikację stanów, w jakich może znajdować się urządzenie oraz przejść między tymi stanami.

Stan można traktować jako bieżący zestaw danych opisujących pamięć urządzenia od momentu jego włączenia. Na podstawie tych danych urządzenie może wykonywać określone czynności lub inaczej reagować na dane wprowadzane przez użytkownika.

Przejście to warunek logiczny, który, gdy jest prawdziwy, powoduje zmianę stanu urządzenia. Przejście może być z jednego stanu do dowolnego innego stanu. Stan może mieć wiele przejść.

Powyższy schemat określa następujące stany:

  • Nieprzypisany
  • Posłuchaj A
  • Posłuchaj B
  • Zespół A
  • Zespół B

Urządzenie uruchamiające kod gry może znajdować się w jednym z tych pięciu stanów, ale tylko w jednym na raz i tylko w tych pięciu.

W całym przewodniku zakładam, że używasz edytora MakeCode firmy Microsoft, który można znaleźć pod adresem:

Pełną implementację gry można znaleźć tutaj:

makecode.microbit.org/_CvRMtheLbRR3 ("microbit-demo-user" to nazwa projektu)

A implementację głównego ("root") kontrolera sieciowego można znaleźć tutaj:

makecode.microbit.org/_1kKE6TRc9TgE ("microbit-demo-root" to nazwa projektu)

Do tych przykładów będę się odwoływał w moim samouczku.

Krok 1: Rozważania dotyczące projektowania dużego obrazu

Zanim napiszemy jakikolwiek kod, musimy zastanowić się, jak chcemy, aby wyglądał nasz produkt końcowy. innymi słowy, jakie są wymagania aplikacji? Co nasz kod powinien powiedzieć urządzeniu po zakończeniu? Funkcjonalność głównej aplikacji podzieliłem na sześć kategorii, z których każdą można rozpatrywać z innej perspektywy projektowej.

  1. Chcemy kontrolować działania urządzenia na podstawie jego aktualnego stanu
  2. Chcemy, aby urządzenie reagowało na polecenia użytkownika
  3. Możemy chcieć wyświetlać animacje i grafikę za pomocą wyświetlacza LED 5 x 5
  4. Chcemy inicjalizować wartości danych w pamięci urządzenia podczas uruchamiania urządzenia
  5. Chcemy przesyłać dane bezprzewodowo za pomocą radia urządzenia
  6. Chcemy nasłuchiwać i odbierać dane przez radio urządzenia i odpowiednio je przetwarzać

Pozwólcie, że omówię nieco więcej szczegółów na temat każdego z nich.

1. Chcemy kontrolować działania urządzenia na podstawie jego aktualnego stanu

Podobnie jak większość innych programów, wykonanie instrukcji określonych przez kod odbywa się po jednym wierszu na raz. Chcemy, aby nasze urządzenie wykonywało określone instrukcje na podstawie swojego stanu wewnętrznego, co ilustruje diagram na początku tego samouczka. Moglibyśmy napisać serię warunków po każdym bloku kodu, który sprawdza, czy urządzenie powinno działać, ale takie podejście może bardzo szybko stać się bardzo nieuporządkowane, więc zamiast tego użyjemy nieskończonej pętli, która po prostu sprawdza jedną zmienną i opartą na tej zmiennej, wykonuje określony zestaw instrukcji lub nie robi nic. Ta zmienna będzie identyfikowana przez przyrostek „_state” zarówno w naszej aplikacji użytkownika, jak iw naszej aplikacji root.

2. Chcemy, aby urządzenie reagowało na polecenia użytkownika

Mimo normalnego wykonywania kodu, który odbywa się sekwencyjnie, czyli po jednej linii na raz, potrzebujemy, aby nasze urządzenie reagowało na naciśnięcia przycisków, podczas gdy główna pętla stanu określa, co urządzenie powinno w danym momencie zrobić. W tym celu urządzenie ma możliwość wysyłania sygnałów do oprogramowania niższego poziomu, które współdziała ze sprzętem, wyzwalając to, co nazywamy zdarzeniem. Możemy napisać kod, który mówi urządzeniu, aby coś zrobiło, gdy wykryje określony rodzaj zdarzenia.

3. Chcemy wyświetlać animacje i grafikę za pomocą wyświetlacza LED 5 x 5

Mechanizm do tego wydaje się być prosty, ale blok wyświetlający obraz dodaje ukryte opóźnienie 400 ms. Ponieważ chcemy, aby nasze urządzenie kontynuowało wykonywanie pętli stanu z jak najmniejszym opóźnieniem, będziemy musieli edytować kod javascript, aby zminimalizować opóźnienie.

4. Chcemy inicjalizować wartości danych w pamięci urządzenia podczas uruchamiania urządzenia

Zanim nasze urządzenie cokolwiek zrobi, aplikacja musi załadować swoje dane do pamięci. Obejmuje to zmienne stałe nazwane dla czytelności kodu, zmienne zawierające obrazy, które mogą być częścią animacji, oraz zmienne liczników, które muszą zaczynać się od 0, aby działały poprawnie. Skończymy z długą listą nazw zmiennych i ich nowo przypisanych wartości. Jako osobisty wybór stylu oznaczę wartości stałe, tj. wartości, których nigdy nie będę musiał zmieniać, używając ALL_CAPS. Będę również poprzedził identyfikatory zmiennych głównych nazwą kategorii, która odnosi się do rodzaju obiektu lub typu, do którego należy identyfikator. Ma to na celu ułatwienie śledzenia kodu. Nigdy nie użyję nazwy zmiennej, takiej jak „item” lub „x”, z powodu niejednoznaczności, która pojawia się podczas próby odszyfrowania kodu.

5. Chcemy przesyłać dane bezprzewodowo za pomocą radia urządzenia

W rzeczywistości jest to dość proste zadanie przy użyciu języka bloków MakeCode. Po prostu ustawiamy wszystkie urządzenia na tę samą grupę radiową w czasie rozruchu, a następnie, gdy chcemy wysłać sygnał, możemy przekazać pojedynczy numer do dostarczonego nam bloku „Radio send number”. Ważne jest, aby nadawca i odbiorca pracowały na tej samej grupie radiowej, ponieważ w przeciwnym razie będą nadawali lub odbierali na różnych częstotliwościach, a komunikacja nie powiedzie się.

6. Chcemy nasłuchiwać i odbierać dane przez radio urządzenia i odpowiednio je przetwarzać

Mając na uwadze te same rozważania, co w poprzednim punkcie, będziemy nasłuchiwać przychodzących transmisji w taki sam sposób, w jaki nasłuchujemy danych wejściowych użytkownika: za pomocą procedury obsługi zdarzeń. Napiszemy blok kodu, który przeanalizuje wszystkie przychodzące sygnały i sprawdzi, czy należy podjąć jakieś działanie bez zakłócania głównej pętli stanu.

Ponadto powinniśmy krótko rozważyć projekt znacznie prostszej aplikacji root, programu, który pozwoli urządzeniu kontrolować całą sieć. Nie będę nad tym spędzał dużo czasu, ponieważ jest znacznie prostszy niż powyższy projekt i wiele z tego to po prostu powtórzenia. Funkcjonalność korzenia podzieliłem na trzy kategorie.

  1. Chcemy móc wybrać sygnał
  2. Chcemy móc przekazać sygnał

-

1. Chcemy móc wybrać sygnał

Można to zrobić, po prostu mając przycisk, który przechodzi przez możliwe sygnały. Ponieważ są tylko trzy, to podejście wystarczy. Jednocześnie możemy mieć pętlę, która stale ponownie wyświetla wybrany sygnał, pozwalając użytkownikowi nacisnąć przycisk i zobaczyć, jak wybrany sygnał pojawia się na wyświetlaczu LED z bardzo małym opóźnieniem.

2. Chcemy móc przekazać sygnał

Ponieważ są dwa przyciski, możemy wyznaczyć jeden do wyboru, a drugi do potwierdzenia. Podobnie jak aplikacja użytkownika, po prostu wysyłamy sygnał przez sieć jako numer. Nie są wymagane żadne inne informacje.

Więcej o prostym protokole sygnałowym powiem w następnej sekcji.

Krok 2: Protokół sygnałowy: prosty język komunikacji sieciowej

Poniższe sygnały można traktować jako zbiór wszystkich możliwych słów, których urządzenia mogą używać do komunikowania się ze sobą. Ponieważ sieć jest tak prosta, nie ma wiele do powiedzenia, więc możemy przedstawić te trzy sygnały za pomocą prostych wartości całkowitych.

0. Zresetuj

  • Identyfikator w kodzie: SIG-R
  • Wartość całkowita: 0
  • Cel: Powiedz wszystkim urządzeniom w zasięgu, aby porzuciły to, co robią i zachowywały się tak, jakby właśnie zostały uruchomione. Jeśli ten sygnał dotrze do każdego urządzenia w sieci, cała sieć zostanie zresetowana, a użytkownicy będą mogli rozpocząć nową grę. Ten sygnał może być nadawany tylko przez urządzenie root.

1. Konwersja A

  • Identyfikator w kodzie: SIG-A
  • Wartość całkowita: 1
  • Cel: Poinformuj każde urządzenie, które jest w stanie LISTEN_A, po otrzymaniu sygnału konwersji, aby przełączyło się w stan TEAM_A.

2. Konwersja B

  1. Identyfikator w kodzie: SIG-B
  2. Wartość całkowita: 2
  3. Cel: Powiedz każdemu urządzeniu, które jest w stanie LISTEN_B, po otrzymaniu sygnału konwersji, aby przełączyło się w stan TEAM_B.

Krok 3: Chcemy kontrolować działania urządzenia na podstawie jego aktualnego stanu

Chcemy kontrolować działania urządzenia na podstawie jego aktualnego stanu
Chcemy kontrolować działania urządzenia na podstawie jego aktualnego stanu
Chcemy kontrolować działania urządzenia na podstawie jego aktualnego stanu
Chcemy kontrolować działania urządzenia na podstawie jego aktualnego stanu
Chcemy kontrolować działania urządzenia na podstawie jego aktualnego stanu
Chcemy kontrolować działania urządzenia na podstawie jego aktualnego stanu

Nareszcie możemy zacząć pisać kod.

Najpierw otwórz nowy projekt w Make Code

  • Utwórz nową funkcję. Nazwałem moją pętlę, ponieważ jest to główna pętla aplikacji
  • Dodaj blok pętli, który będzie się powtarzał w nieskończoność. Użyłem while(true), ponieważ dosłowna prawda nigdy nie będzie fałszem, stąd przepływ sterowania aplikacji nigdy nie wyjdzie z pętli
  • Dodaj wystarczającą liczbę bloków if-else, aby sprawdzić, czy urządzenie znajduje się w jednym z pięciu możliwych stanów
  • Utwórz zmienną do przechowywania bieżącego stanu urządzenia
  • Utwórz zmienne reprezentujące każdy z pięciu możliwych stanów

    Uwaga: To dobrze, że te zmienne nie mają jeszcze żadnych przypisanych wartości. Dojdziemy do tego. W tym momencie ważniejsze jest, abyśmy napisali czysty, łatwy do odczytania kod

  • Zmień każdy warunek w blokach if-else, aby porównać bieżący stan z jednym z możliwych stanów
  • Na dole bloków if-else dodaj pauzę na pewną liczbę milisekund i utwórz zmienną do przechowywania tej liczby. Zainicjujemy go później. Upewnij się, że zmienna ma opisową nazwę, taką jak tik lub puls. Ponieważ jest to główna pętla urządzenia, ta pauza określi prędkość, z jaką urządzenie wykonuje główną pętlę, więc jest to bardzo ważna wartość i jest zbyt ważna, aby być magiczną liczbą bez nazwy.

Uwaga: Nie przejmuj się szarymi blokami na trzecim obrazie. Zajmę się nimi później.

Krok 4: Chcemy zareagować na dane wprowadzone przez użytkownika

Chcemy reagować na dane wprowadzone przez użytkownika
Chcemy reagować na dane wprowadzone przez użytkownika
Chcemy reagować na dane wprowadzone przez użytkownika
Chcemy reagować na dane wprowadzone przez użytkownika

Teraz chcemy powiedzieć urządzeniu, jak radzić sobie z naciśnięciami przycisków. Pierwszą myślą może być po prostu użycie bloków „Kiedy przycisk jest wciśnięty” w kategorii danych wejściowych, ale chcielibyśmy bardziej szczegółowej kontroli. Użyjemy bloku "na zdarzeniu od (X) o wartości (Y)" z kategorii kontroli w sekcji zaawansowanej, ponieważ w tym samouczku jesteśmy zaawansowani.

  • Utwórz cztery bloki „na wydarzeniu z…”.

    • Dwa z nich powinny sprawdzać źródło zdarzenia „MICROBIT_ID_BUTTON_A”
    • Dwa z nich powinny sprawdzać źródło zdarzenia „MICROBIT_ID_BUTTON_B”
    • Z dwóch zdarzeń kierowanych na każdy przycisk:

      • Należy sprawdzić zdarzenie typu „MICROBIT_BUTTON_EVT_UP”
      • Należy sprawdzić zdarzenie typu „MICROBIT_BUTTON_EVT_DOWN”
    • Uwaga: Te opcje pisane wielkimi literami to etykiety używane w kodzie mikro:bitowym niższego poziomu. Są to po prostu symbole zastępcze, które są później zastępowane liczbami całkowitymi, gdy kod jest kompilowany do wykonywalnego pliku binarnego. Ludziom łatwiej jest używać tych etykiet niż sprawdzać, którą liczbę całkowitą wstawić, chociaż obie działają w ten sam sposób.
  • Zdecydowałem się, ze względu na styl, aby każdy blok „w zdarzeniu od…” wywoływał funkcję opisującą zgłoszone zdarzenie. Chociaż nie jest to bezwzględnie konieczne, moim zdaniem poprawia to czytelność. Jeśli ktoś chce to zrobić, może umieścić kod obsługi zdarzeń wewnątrz samego bloku „on event from…”.

    Uwaga: blok kodu, który obsługuje odpowiedź urządzenia na zdarzenie, jest intuicyjnie nazywany „obsługą zdarzenia”

  • Dodaj w każdym programie obsługi zdarzeń tę samą strukturę if-else, która służy do dzielenia przepływu sterowania na podstawie stanu urządzenia, co struktura w głównej pętli stanu.
  • Dodaj bloki przypisania, które modyfikują stan urządzenia zgodnie z naszym diagramem stanów

    • Wiemy, że gdy urządzenie jest w stanie NIEPRZYPISANY, urządzenie powinno reagować na wciśnięcie przycisku A przejściem do stanu LISTEN_A, a na wciśnięcie przycisku B przejściem do stanu LISTEN_B
    • Wiemy również, że gdy urządzenie znajduje się w stanie LISTEN_A lub LISTEN_B, powinno zareagować odpowiednio na zwolnienie przycisku A i przycisku B, przechodząc z powrotem do stanu NIEPRZYPISANE.
    • Na koniec wiemy, że gdy urządzenie znajduje się w stanie TEAM_A lub TEAM_B, urządzenie powinno reagować na wciśnięcie przycisku A i wciśnięcie przycisku B poprzez emisję SIG_A i odpowiednio SIG_B.

      W tym miejscu nie jest konieczne wypełnianie szczegółów dotyczących sygnałów nadawczych. Dojdziemy do tego później. Ważne jest to, że instruujemy te funkcje, aby używały kodu, który napiszemy, nadając temu blokowi akcji nazwę, taką jak broadcastSignalSIG_A, która opisuje, co należy zrobić w tym momencie

Krok 5: Chcemy zainicjować wartości danych w pamięci urządzenia po uruchomieniu urządzenia

Chcemy inicjalizować wartości danych w pamięci urządzenia po uruchomieniu urządzenia
Chcemy inicjalizować wartości danych w pamięci urządzenia po uruchomieniu urządzenia
Chcemy inicjalizować wartości danych w pamięci urządzenia po uruchomieniu urządzenia
Chcemy inicjalizować wartości danych w pamięci urządzenia po uruchomieniu urządzenia
Chcemy inicjalizować wartości danych w pamięci urządzenia po uruchomieniu urządzenia
Chcemy inicjalizować wartości danych w pamięci urządzenia po uruchomieniu urządzenia

W tym momencie użyliśmy wielu zmiennych (nazw danych), ale tak naprawdę nie przypisaliśmy im wartości. Chcemy, aby urządzenie ładowało wartości wszystkich tych zmiennych do pamięci podczas rozruchu, więc umieszczamy inicjalizację tych zmiennych w bloku „na starcie”.

Oto wartości, które musimy zainicjować:

  • Stałe sygnału, zgodnie z protokołem sygnału. Wartości MUSZĄ być:

    • SIG_R = 0
    • SIG_A = 1
    • SIG_B = 2
    • Uwaga: Poprzedziłem te stałe przedrostkiem „EnumSignals”, aby wskazać, że te zmienne mają zachowywać się tak, jakby były częścią wyliczonego typu o nazwie Sygnały. W ten sposób zmienne te mogą być zaimplementowane w innych językach programowania. Definicja i wyjaśnienie typów wyliczonych wykracza poza zakres mojego samouczka. Można go wygooglować, jeśli sobie tego życzą. Te przedrostki są po prostu stylistycznymi wyborami i wcale nie są niezbędne do prawidłowego funkcjonowania programu.
  • Stałe stanu, które mogą być dowolne, o ile mają wartość. Dokonałem wyboru stylu, aby po prostu używać liczb całkowitych rosnących od 0, na przykład:

    • NIEPRZYPISANE = 0
    • SŁUCHAJ_A = 1
    • SŁUCHAJ_B = 2
    • DRUŻYNA_A = 3
    • ZESPÓŁ_B = 4
    • Uwaga: podjąłem tę samą decyzję dotyczącą stylu w odniesieniu do przedrostków dla tych zmiennych. Dodatkowo wspomnę, że wszystko dotyczące tych przydziałów, wartości i kolejności, jest całkowicie arbitralne. Nie ma nawet znaczenia, że wartości te są spójne w zależności od urządzenia, ponieważ są używane tylko wewnętrznie, a nie do komunikacji przez sieć. Liczy się tylko to, że zmienne mają wartość i można je ze sobą porównać, aby sprawdzić, czy są równoważne, czy nie.
  • Aby uzyskać czytelność, stała o nazwie BOOT_STATE i ustaw ją na UNASSIGNED. To sprawia, że fakt, że resetujemy się do stanu rozruchu, zamiast do stanu bardziej arbitralnego, jest bardziej jednoznaczny, gdy urządzenie otrzyma sygnał resetu, który zaimplementujemy później.
  • Stałe animacji, używane w następnym kroku do tworzenia animacji, które pozwalają na bardzo małe opóźnienia przy wprowadzaniu przez użytkownika. Do tej pory ich nie używaliśmy, ale z pewnością zostaną wyjaśnione i użyte w następnym rozdziale. Znaczenie niektórych z nich powinno być intuicyjne ze względu na ich nazwy.

    • TICKS_PER_FRAME_LOADING_ANIMATION = 50
    • MS_PER_DEVICE_TICK = 10
    • MS_PER_FRAME_BROADCAST_ANIMATION = 500
    • MICROSECONDS_PER_MILLISECOND = 1000
    • NUMBER_OF_FRAMES_IN_LOADING_ANIMATION = 4
  • Kolejna zmienna dla animacji, tym razem licznik, który zdecydowanie nie jest stały. Jak większość liczników, inicjujemy go na 0

    iTickLoadingAnimacja = 0

  • Utwórz dwie serie zmiennych do przechowywania klatek animacji. Pierwszy, który nazywam „animacją ładowania”, powinien mieć cztery obrazy (co można się domyślić przy ostatniej stałej inicjalizacji), a drugi, który nazywam „animacją rozgłaszania”, który powinien mieć trzy obrazy. Polecam nazywanie zmiennych tak, aby odpowiadały ramkom animacji, np. pierścieńAnimacja0, pierścieńAnimacja1…

    Utwórz takie same wartości obrazu jak ja lub stwórz bardziej oryginalne i fajniejsze obrazy

  • Na koniec musimy ustawić grupę radiową urządzenia na 0 za pomocą bloku „grupa ustawień radiowych (X)”
  • Opcjonalnie napisz komunikat „Inicjalizacja zakończona” na wyjściu szeregowym, aby poinformować użytkownika, że wszystko poszło gładko.
  • Teraz, gdy skończyliśmy konfigurować urządzenie, możemy wywołać naszą funkcję pętli stanu.

Krok 6: Chcemy wyświetlać animacje i grafikę za pomocą wyświetlacza LED 5 X 5

Chcemy wyświetlać animacje i grafikę za pomocą wyświetlacza LED 5 X 5
Chcemy wyświetlać animacje i grafikę za pomocą wyświetlacza LED 5 X 5
Chcemy wyświetlać animacje i grafikę za pomocą wyświetlacza LED 5 X 5
Chcemy wyświetlać animacje i grafikę za pomocą wyświetlacza LED 5 X 5
Chcemy wyświetlać animacje i grafikę za pomocą wyświetlacza LED 5 X 5
Chcemy wyświetlać animacje i grafikę za pomocą wyświetlacza LED 5 X 5

A teraz coś z zupełnie innej beczki.

Chcemy wyświetlić kilka animacji i kilka postaci, ale nie chcemy przerywać głównej pętli stanu. Niestety bloki wyświetlające obrazy i ciągi tekstowe mają domyślnie opóźnienie 400 ms. Nie można tego zmienić bez edytowania reprezentacji kodu w javascript. A więc to właśnie zrobimy.

  • Utwórz funkcję dla każdego obrazu. Umożliwi to użycie pojedynczego bloku do wyświetlania obrazu zamiast edycji javascript za każdym razem. W tym konkretnym programie żaden obraz nie jest używany więcej niż raz, ale nadal uważam, że ten styl sprawia, że kod jest łatwiejszy do odczytania.
  • Dodaj, w każdej nowej funkcji, blok „pokaż obraz (X) przy przesunięciu 0” z odpowiednią nazwą zmiennej obrazu zastępującą (X)
  • Dodaj w głównej pętli stanu. Bloki „Pokaż ciąg (X)” do każdego bloku oprócz tego, który obsługuje stan NIEPRZYPISANY. Dodaj znak dla urządzenia do wyświetlenia, aby wskazać jego różne stany. Oto co zrobiłem:

    • LISTEN_A: „a”
    • LISTEN_B: „b”
    • TEAM_A: 'A'
    • ZESPÓŁ_B: „B”

      W przypadku stanu UNASSIGNED należy wywołać funkcję, która zaktualizuje animację ładowania. Poniżej wypełnimy szczegóły tej funkcji

  • Przełącz na tryb JavaScript.
  • Znajdź każde wywołanie X.showImage(0) i basic.showString(X)
  • Zmień każdy z nich na X.showImage(0,0) lub basic.showString(X,0)

    • Dodanie tego dodatkowego argumentu ustawi opóźnienie po akcji na 0. Domyślnie jest to pominięte, a urządzenie zatrzyma się na 400 ms po wykonaniu każdego z tych bloków.
    • Teraz mamy prawie wolny od opóźnień mechanizm wyświetlania naszych obrazów w naszych blokach animacji, które możemy teraz zbudować

Najpierw zbudujemy stosunkowo prostą funkcję animacji transmisji. Jest to prostsze, ponieważ nie chcemy, aby użytkownik mógł cokolwiek zrobić, dopóki funkcja nie zostanie ukończona, aby powstrzymać go przed spamowaniem funkcji rozgłaszania. Aby to osiągnąć, możemy po prostu ograniczyć przepływ sterowania do bloku, dopóki funkcja nie zostanie ukończona, co jest standardowym zachowaniem.

  • Utwórz funkcję, która będzie wyświetlać animację transmisji.
  • Wewnątrz tego bloku dodaj trzy wywołania funkcji, po jednym dla każdej klatki animacji, w kolejności, w jakiej powinny być wyświetlane
  • Dodaj blok "czekaj (nas) (X)" po każdym wywołaniu funkcji wyświetlania obrazu.

    Uwaga: Ten blok, z sekcji zaawansowanego sterowania, pójdzie nawet dalej niż „pauza (ms)” w tym sensie, że całkowicie zamrozi procesor do czasu upłynięcia określonego czasu. Gdy używana jest blokada pauzy, możliwe jest, że urządzenie będzie wykonywać inne zadania za kulisami. Jest to niemożliwe z blokiem oczekiwania

  • Zamień (X) na (MS_PER_FRAME_BROADCAST_ANIMATION x MICROSECONDS_PER_MILLISECOND)
  • Animacja powinna teraz działać poprawnie

Po drugie, zbudujemy mechanizm wyświetlania animacji ładowania. Ideą tego jest aktualizowanie wyświetlacza LED w określonych odstępach czasu, które definiujemy w zmiennej MS_PER_DEVICE_TICK. Ta wartość, długość taktu urządzenia, to liczba milisekund, które urządzenie wstrzymuje po zakończeniu każdej iteracji pętli stanu. Ponieważ ta wartość jest wystarczająco mała, możemy aktualizować wyświetlacz raz podczas każdej iteracji pętli wyświetlania i użytkownik będzie miał wrażenie, że animacja przebiega płynnie, a gdy stan się zmieni, opóźnienie między wejściem użytkownika będzie bardzo małe aktualizowany wyświetlacz. Licząc tiki, co robimy za pomocą zmiennej iTickLoadingAnimation, możemy wyświetlić odpowiednią klatkę animacji.

  • Utwórz funkcję, która zaktualizuje animację ładowania
  • Dodaj warunek, aby sprawdzić, czy licznik tików osiągnął maksymalną wartość. Ten warunek będzie spełniony, jeśli wartość licznika tików jest większa niżliczba ramek w animacji ładowania pomnożona przez liczbętaktów do wyświetlenia każdej klatki

    Jeśli warunek jest spełniony, zresetuj iTickLoadingAnimation do 0

  • Dodaj blok warunków if-else. Określają one klatkę animacji do wyświetlenia.

    Dla każdej klatki animacji, jeśli licznik taktów jest mniejszy niż liczba taktów w każdej animacji pomnożona przez numer klatki animacji (zaczynając od 1), wyświetl tę klatkę, w przeciwnym razie sprawdź, czy następna klatka jest tą, być wyświetlanym

  • Na dole bloku zwiększ iTickLoadingAnimation
  • Animacja powinna teraz działać poprawnie

Uwaga: Wszystkie szare bloki, które pojawiają się w moim przykładzie, są generowane, gdy edytuje się reprezentację bloku w javascript. Oznacza to po prostu, że blok reprezentuje kod javascript, który nie może być reprezentowany za pomocą standardowego zestawu bloków i musi być edytowany w formie tekstowej.

Krok 7: Chcemy przesyłać dane bezprzewodowo za pomocą radia urządzenia

Chcemy przesyłać dane bezprzewodowo za pomocą radia urządzenia
Chcemy przesyłać dane bezprzewodowo za pomocą radia urządzenia

Ten krok jest znacznie krótszy niż poprzedni. W rzeczywistości jest to prawdopodobnie najkrótszy krok w całym tym samouczku.

Przypomnij sobie, że kiedy zaprogramowaliśmy reakcję urządzenia na dane wprowadzone przez użytkownika, na zrzucie ekranu pojawiły się dwa bloki, które nie zostały wyjaśnione w tej sekcji. Były to wywołania funkcji, które wysyłają sygnały przez radio. Dokładniej:

  • Po naciśnięciu przycisku A:

    • Jeśli urządzenie jest w stanie TEAM_A:

      Sygnał transmisji SIG_A

  • Po naciśnięciu przycisku B:

    • Jeśli urządzenie jest w stanie TEAM_B

      Sygnał transmisji SIG_B

Utwórz te funkcje, jeśli jeszcze nie istnieją.

W każdej funkcji:

  • Wywołaj funkcję animacji transmisji. To zablokuje wszystko inne, dopóki nie zostanie ukończone, co będzie w MS_PER_FRAME_BROADCAST_ANIMATION * 3 = 1,5 sekundy. Stała jest mnożona przez trzy, ponieważ animacja zawiera trzy klatki. Jest to arbitralne i można dodać więcej, jeśli estetyka jest wystarczająco duża. Drugim celem tej animacji jest zapobieganie spamowaniu przez użytkownika funkcji rozgłaszania.
  • Dodaj blok „numer wysyłki radiowej (X)”, gdzie jest stała sygnału wymieniona w nazwie funkcji

To wszystko, co trzeba, żeby nadawać przez radio.

Krok 8: Chcemy nasłuchiwać i odbierać dane przez radio urządzenia i odpowiednio je przetwarzać

Chcemy nasłuchiwać i odbierać dane przez radio urządzenia i odpowiednio je przetwarzać
Chcemy nasłuchiwać i odbierać dane przez radio urządzenia i odpowiednio je przetwarzać
Chcemy nasłuchiwać i odbierać dane przez radio urządzenia i odpowiednio je przetwarzać
Chcemy nasłuchiwać i odbierać dane przez radio urządzenia i odpowiednio je przetwarzać

To jest ostatni krok do stworzenia głównej aplikacji.

Powiemy urządzeniu, jak przetwarzać przychodzące sygnały radiowe. Najpierw nasze urządzenie będzie nazywać odbierany sygnał. Następnie, na podstawie wartości tego sygnału, zdecyduje, jakie działania podjąć, jeśli w ogóle.

Najpierw:

  1. Utwórz blok kodu rozpoczynający się od bloku „odebrano przez radio (X)”.
  2. Opcjonalnie przypisz otrzymaną wartość do innej zmiennej o bardziej opisowej nazwie.
  3. Wywołaj funkcję, która przetworzy sygnał

Po drugie, w funkcji przetwarzania sygnału:

  1. Utwórz blok instrukcji if-else, które rozgałęziają przepływ sterowania na podstawie wartości sygnału.
  2. Jeśli sygnał był SIG_R

    Ustaw stan urządzenia na BOOT_STATE (dlatego utworzyliśmy tę stałą wcześniej)

  3. Jeśli sygnał to SIG_A, a aktualny stan to LISTEN_A

    Ustaw stan urządzenia na TEAM_A

  4. Jeśli sygnał to SIG_B, a aktualny stan to LISTEN_B

    Ustaw stan urządzenia na TEAM_B

Otóż to. Aplikacja jest zakończona.

Krok 9: Urządzenie root: chcemy móc wybrać sygnał

Urządzenie root: chcemy móc wybrać sygnał
Urządzenie root: chcemy móc wybrać sygnał

Teraz napiszemy prostą aplikację dla urządzenia „root”, czyli urządzenia, które będzie sterować siecią.

To urządzenie będzie musiało wykonywać dwie funkcje:

  • Chcemy umożliwić użytkownikowi wybór jednego z naszych sygnałów
  • Chcemy umożliwić użytkownikowi nadawanie sygnału

Ponieważ specyfikacja tej aplikacji jest podzbiorem poprzedniej, przedstawię przegląd, ale nie będę wchodzić w tak wiele szczegółów, jak wcześniej. Powyższy obrazek zawiera kompletny kod dla tej aplikacji.

Aby umożliwić użytkownikowi wybór sygnału:

  1. Zainicjuj 5 zmiennych w bloku „na starcie”:

    1. Trzy sygnały (0, 1, 2)
    2. Liczba sygnałów (3)
    3. Zmienna do przechowywania aktualnie wybranego sygnału (początkowo ustawiona na pierwszy sygnał, 0)
  2. Obsługuj naciśnięcie przycisku A:

    1. Zwiększ wybrany sygnał
    2. Sprawdź, czy wybrany sygnał jest większy lub równy liczbie sygnałów

      Jeśli tak, ustaw wybrany sygnał na 0

  3. Po bloku on start uruchom pętlę „na zawsze”, która wyświetla aktualnie wybraną wartość sygnału bez opóźnienia

Aby umożliwić użytkownikowi nadawanie sygnału

  1. Ustaw grupę radiową na 0 w bloku „na starcie”
  2. Obsługuj naciśnięcie przycisku B:

    Przesyłaj wybrany sygnał za pomocą bloku „numer nadawania radiowego (X)”

Otóż to. Aplikacja węzła głównego jest niezwykle prosta.

Krok 10: Jesteśmy skończeni

Skończyliśmy
Skończyliśmy

Powyżej znajduje się zdjęcie urządzeń, na których działa aplikacja. Dwa po prawej uruchamiają główną aplikację "użytkownika", a ten po lewej uruchamia aplikację "root".

Zademonstrowałem tę grę na CS Connections 2018, tygodniowej letniej konferencji dla nauczycieli gimnazjów i liceów poświęconej edukacji informatycznej. Rozdałem nauczycielom około 40 urządzeń i wyjaśniłem zasady. Większość uznała tę grę za zabawną, a wielu uważało ją za mylącą, dopóki nie zorientowali się, jak grać. Demonstracja była krótka, ale uznaliśmy, że gra była przyjemna wśród dość zróżnicowanego tłumu.

Więcej informacji o CS Connections 2018 można znaleźć tutaj.

Zalecana: