Cuprins:

Rivalitate în rețea: un joc cu latență redusă pentru BBC Micro: bit: 10 pași (cu imagini)
Rivalitate în rețea: un joc cu latență redusă pentru BBC Micro: bit: 10 pași (cu imagini)

Video: Rivalitate în rețea: un joc cu latență redusă pentru BBC Micro: bit: 10 pași (cu imagini)

Video: Rivalitate în rețea: un joc cu latență redusă pentru BBC Micro: bit: 10 pași (cu imagini)
Video: Tenda MX12 (1, 2 sau 3 pack) | Sistem mesh Wi-Fi 6 Whole-Home Gigabit AX3000 2024, Iulie
Anonim
Rivalitate în rețea: un joc cu latență redusă pentru BBC Micro: bit
Rivalitate în rețea: un joc cu latență redusă pentru BBC Micro: bit
Rivalitate în rețea: un joc cu latență redusă pentru BBC Micro: bit
Rivalitate în rețea: un joc cu latență redusă pentru BBC Micro: bit

În acest tutorial, voi explica cum să implementați un joc multiplayer de bază pe BBC micro: bit cu următoarele caracteristici:

  • O interfață simplă
  • Latență scăzută între apăsarea butoanelor și actualizări de ecran
  • Un număr flexibil de participanți
  • Control ușor asupra jocului folosind un dispozitiv master („root”)

Jocul este în esență o simulare a politicii. Toți jucătorii încep neatribuiți la nicio echipă, cu excepția a doi jucători. Unul dintre acești jucători este alocat echipei A, iar celălalt este alocat echipei B.

Obiectivul jocului pentru fiecare jucător de a fi în echipă cu majoritatea jucătorilor în momentul în care toată lumea este convertită.

Diagrama de mai sus ilustrează o mașină cu stări finite, adică o specificație a stărilor în care poate fi dispozitivul și a tranzițiilor dintre acele stări.

O stare poate fi considerată ca setul curent de date care descrie memoria dispozitivului de când a fost pornit. Pe baza acestor date, dispozitivul poate efectua anumite acțiuni sau poate reacționa diferit la introducerea utilizatorului.

O tranziție este o condiție logică care, atunci când este adevărată, determină dispozitivul să-și schimbe starea. O tranziție poate fi de la un stat la orice alt stat. Un stat poate avea mai multe tranziții.

Diagrama de mai sus specifică următoarele stări:

  • Neatribuit
  • Ascultați pentru A
  • Ascultă B
  • Echipa A
  • Echipa B

Un dispozitiv care rulează codul de joc poate fi în oricare dintre aceste cinci stări, dar numai una câte una, și numai aceste cinci.

Voi presupune în tot ghidul că utilizați editorul Microsoft MakeCode, care poate fi găsit la:

Implementarea completă a jocului poate fi găsită aici:

makecode.microbit.org/_CvRMtheLbRR3 („microbit-demo-user” este numele proiectului)

Și implementarea controlerului de rețea master („rădăcină”) poate fi găsită aici:

makecode.microbit.org/_1kKE6TRc9TgE („microbit-demo-root” este numele proiectului)

Mă voi referi la aceste exemple pe parcursul tutorialului meu.

Pasul 1: Considerații privind proiectarea imaginilor mari

Înainte de a scrie orice cod, trebuie să ne gândim la cum dorim să arate produsul nostru final. cu alte cuvinte, care sunt cerințele aplicației? Ce ar trebui să spună codul nostru dispozitivului să facă când este terminat? Am împărțit funcționalitatea aplicației principale în șase categorii, fiecare dintre acestea putând fi considerată dintr-o perspectivă de proiectare diferită.

  1. Vrem să controlăm acțiunile dispozitivului pe baza stării sale actuale
  2. Vrem ca dispozitivul să reacționeze la intrarea utilizatorului
  3. Este posibil să dorim să afișăm animații și grafică folosind afișajul LED 5 x 5
  4. Vrem să inițializăm valorile datelor în memoria dispozitivelor atunci când dispozitivul pornește
  5. Vrem să transmitem date fără fir folosind radioul dispozitivului
  6. Vrem să ascultăm și să primim date prin intermediul radioului dispozitivului și să le procesăm în consecință

Permiteți-mi să intru mai puțin în detaliu despre fiecare.

1. Vrem să controlăm acțiunile dispozitivului pe baza stării sale actuale

La fel ca majoritatea celorlalte programe, executarea instrucțiunilor specificate de cod are loc rând pe rând. Vrem ca dispozitivul nostru să execute anumite instrucțiuni pe baza stării sale interne, așa cum este ilustrat în diagrama din partea de sus a acestui tutorial. Am putea scrie o serie de condiționare după fiecare bloc de cod care verifică dacă dispozitivul ar trebui să facă, dar această abordare poate deveni foarte dezordonată foarte repede, așa că vom folosi în schimb o buclă infinită care verifică pur și simplu o variabilă și se bazează pe acea variabilă, execută un set specific de instrucțiuni sau nu face nimic. Această variabilă va fi identificată prin sufixul „_state” atât în aplicația noastră de utilizator, cât și în aplicația noastră rădăcină.

2. Vrem ca dispozitivul să reacționeze la intrarea utilizatorului

În ciuda executării normale a codului care are loc secvențial, adică o linie la un moment dat, avem nevoie ca dispozitivul nostru să reacționeze la apăsarea butoanelor în timp ce bucla de stare principală stabilește ce ar trebui să facă dispozitivul la un moment dat. În acest scop, dispozitivul are capacitatea de a trimite semnale către software-ul de nivel inferior care interacționează cu hardware-ul, declanșând ceea ce se numește un eveniment. Putem scrie cod care să spună dispozitivului să facă ceva atunci când detectează un anumit tip de eveniment.

3. Vrem să afișăm animații și grafică folosind afișajul LED 5 x 5

Mecanismul pentru a face acest lucru pare a fi simplu, dar blocul afișează o imagine adaugă o întârziere ascunsă de 400 ms. Deoarece vrem ca dispozitivul nostru să-și execute în continuare bucla de stare cu o latență cât mai mică posibil, va trebui să edităm codul javascript pentru a minimiza întârzierea.

4. Vrem să inițializăm valorile datelor în memoria dispozitivelor atunci când dispozitivul pornește

Înainte ca dispozitivul nostru să facă ceva, aplicația trebuie să-și încarce datele în memorie. Aceasta include variabile constante numite pentru lizibilitatea codului, variabile care conțin imagini, care pot face parte dintr-o animație și variabile de contor care trebuie să înceapă de la 0 pentru a funcționa corect. Vom sfârși cu o listă lungă de nume de variabile și valorile nou atribuite acestora. Ca o alegere de stil personal, voi nota valori constante, adică valori pe care nu va trebui să le schimb niciodată, folosind ALL_CAPS. De asemenea, voi prefixa identificatorii variabilelor principale cu un nume de categorie care se referă la un fel de obiect sau tip în care se încadrează identificatorul. Acest lucru este în încercarea de a face codul mai ușor de urmărit. Nu voi folosi niciodată un nume variabil precum „item” sau „x” din cauza ambiguității care apare atunci când încerc să descifrez codul.

5. Vrem să transmitem date fără fir folosind radioul dispozitivului

Aceasta este de fapt o sarcină destul de simplă atunci când se utilizează limbajul blocurilor MakeCode. Pur și simplu setăm toate dispozitivele la același grup de radio la momentul pornirii și apoi, atunci când dorim să trimitem un semnal, putem trece un singur număr la blocul „Număr de trimitere radio” furnizat nouă. Este important ca expeditorul și receptorul să lucreze la același grup radio, deoarece, dacă nu, vor trimite sau primi pe frecvențe diferite, iar comunicarea va eșua.

6. Vrem să ascultăm și să primim date prin intermediul radioului dispozitivului și să le procesăm în consecință

Luând în considerare aceleași considerații ca elementul anterior, vom asculta transmisiile primite în același mod în care vom asculta introducerea utilizatorului: cu un gestionar de evenimente. Vom scrie un bloc de cod care va examina eventualele semnale primite și vom verifica dacă trebuie luată vreo acțiune fără a deranja bucla de stare principală.

În plus, ar trebui să luăm în considerare pe scurt proiectarea aplicației root mult mai simple, un program care va permite unui dispozitiv să controleze întreaga rețea. Nu voi petrece mult timp pe acest lucru, deoarece este mult mai simplu decât designul de mai sus și o mare parte din el este pur și simplu repetare. Am împărțit funcționalitatea root deice în trei categorii.

  1. Vrem să putem selecta un semnal
  2. Vrem să putem transmite un semnal

-

1. Vrem să putem selecta un semnal

Acest lucru se poate face printr-un simplu buton care iterează semnalele posibile. Deoarece există doar trei, această abordare va fi suficientă. În același timp, putem avea o buclă care afișează în mod constant semnalul selectat, permițând utilizatorului să apese un buton și să vadă semnalul selectat apărând pe afișajul LED cu o latență foarte mică.

2. Vrem să putem transmite un semnal

Deoarece există două butoane, putem desemna unul pentru selecție și celălalt pentru confirmare. La fel ca aplicația pentru utilizator, pur și simplu trimitem semnalul prin rețea ca număr. Nu sunt necesare alte informații.

Voi vorbi mai multe despre protocolul de semnal simplu în secțiunea următoare.

Pasul 2: Protocolul de semnal: un limbaj simplu pentru comunicarea în rețea

Semnalele următoare pot fi considerate ca ansamblul tuturor cuvintelor posibile pe care dispozitivele le pot folosi pentru a vorbi între ele. Deoarece rețeaua este atât de simplă, nu există prea multe de spus și astfel putem reprezenta aceste trei semnale prin valori întregi simple.

0. Resetați

  • Identificator în cod: SIG-R
  • Valoarea întregului: 0
  • Scop: Spuneți tuturor dispozitivelor din raza de acțiune să renunțe la ceea ce fac și să acționeze ca și cum ar fi doar pornite. Dacă acest semnal ajunge la fiecare dispozitiv din rețea, întreaga rețea va fi resetată și utilizatorii pot începe un joc nou. Acest semnal poate fi transmis doar de un dispozitiv root.

1. Conversia A

  • Identificator în cod: SIG-A
  • Valoarea întregului: 1
  • Scop: Spuneți oricărui dispozitiv aflat în starea LISTEN_A, după ce primesc semnalul de conversie, să treacă la starea TEAM_A.

2. Conversia B

  1. Identificator în cod: SIG-B
  2. Valoarea întregului: 2
  3. Scop: Spuneți oricărui dispozitiv aflat în starea LISTEN_B, după ce primesc semnalul de conversie, să treacă la starea TEAM_B.

Pasul 3: dorim să controlăm acțiunile dispozitivului pe baza stării sale actuale

Vrem să controlăm acțiunile dispozitivului pe baza stării sale actuale
Vrem să controlăm acțiunile dispozitivului pe baza stării sale actuale
Vrem să controlăm acțiunile dispozitivului pe baza stării sale actuale
Vrem să controlăm acțiunile dispozitivului pe baza stării sale actuale
Vrem să controlăm acțiunile dispozitivului pe baza stării sale actuale
Vrem să controlăm acțiunile dispozitivului pe baza stării sale actuale

În sfârșit, putem începe să scriem cod.

Mai întâi, deschideți un nou proiect în Make Code

  • Creați o funcție nouă. Am numit bucla mea pentru că aceasta este bucla de bază a aplicației
  • Adăugați un bloc de buclă care se va repeta la nesfârșit. Am folosit while (adevărat) deoarece un adevărat literal nu va fi niciodată fals, prin urmare fluxul de control al aplicației nu va ieși niciodată din buclă
  • Adăugați suficiente blocuri if-else pentru a verifica dacă dispozitivul se află în oricare dintre cele cinci stări posibile
  • Creați o variabilă pentru a păstra starea curentă a dispozitivului
  • Creați variabile pentru a reprezenta fiecare dintre cele cinci stări posibile

    Notă: este OK ca aceste variabile să nu aibă încă valori atribuite. Vom ajunge la asta. În acest moment, este mai important să scriem cod curat, ușor de citit

  • Schimbați fiecare condiție din blocurile if-else pentru a compara starea curentă cu una dintre stările posibile
  • În partea de jos a blocurilor if-else, adăugați o pauză pentru un anumit număr de milisecunde și creați o variabilă pentru a menține acel număr. Îl vom inițializa mai târziu. Asigurați-vă că variabila are un nume descriptiv, cum ar fi bifă sau bătăi ale inimii. Deoarece aceasta este bucla de bază a dispozitivului, această pauză va determina viteza cu care dispozitivul execută bucla principală, deci este o valoare foarte importantă și este prea importantă pentru a fi un număr magic fără nume.

Notă: Nu vă faceți griji cu privire la blocurile gri din a treia imagine. Voi ajunge la acestea mai târziu.

Pasul 4: Vrem să reacționăm la datele introduse de utilizator

Vrem să reacționăm la datele introduse de utilizator
Vrem să reacționăm la datele introduse de utilizator
Vrem să reacționăm la datele introduse de utilizator
Vrem să reacționăm la datele introduse de utilizator

Acum, vrem să îi spunem dispozitivului cum să facă față apăsărilor de butoane. Primul gând ar putea fi să folosim pur și simplu blocurile „Când butonul este apăsat” în categoria de intrare, dar am dori un control mai granular decât atât. Vom folosi blocul „on event din (X) cu valoare (Y)” din categoria de control din secțiunea avansată, deoarece suntem avansați în acest tutorial.

  • Creați patru blocuri „la eveniment din …”.

    • Două dintre acestea ar trebui să verifice sursa evenimentului „MICROBIT_ID_BUTTON_A”
    • Două dintre acestea ar trebui să verifice sursa evenimentului „MICROBIT_ID_BUTTON_B”
    • Dintre cele două evenimente care vizează fiecare buton:

      • Ar trebui să verificați dacă există tipul „MICROBIT_BUTTON_EVT_UP”
      • Ar trebui să verificați dacă există tipul „MICROBIT_BUTTON_EVT_DOWN”
    • Notă: Aceste opțiuni din toate literele mari sunt etichete care sunt utilizate în codul micro: bit de nivel inferior. Ele sunt pur și simplu substituenți care ulterior sunt înlocuiți cu numere întregi atunci când codul este compilat într-un binar executabil. Este mai ușor pentru oameni să folosească aceste etichete decât să caute ce număr întreg să introducă, deși ambele ar funcționa în același mod.
  • Am ales, ca o chestiune de stil, ca fiecare bloc „pe eveniment din …” să apeleze o funcție care descrie evenimentul ridicat. Deși nu este strict necesar, în opinia mea, acest lucru îmbunătățește lizibilitatea. Dacă cineva dorește să facă acest lucru, poate pune codul de gestionare a evenimentelor în interiorul blocului „on event from…”.

    Notă: blocul de cod care gestionează răspunsul dispozitivului la un eveniment este numit intuitiv „gestionarea evenimentelor”

  • Adăugați, în fiecare gestionar de evenimente, aceeași structură if-else utilizată pentru a împărți fluxul de control pe baza stării dispozitivului ca și structura din bucla de stare principală.
  • Adăugați blocuri de atribuire care modifică starea respectivă a dispozitivului, așa cum este specificat în diagrama noastră de stare

    • Știm că atunci când dispozitivul este în stare NESIGNAT, dispozitivul ar trebui să reacționeze la butonul A apăsat printr-o tranziție la starea LISTEN_A și la butonul B apăsat printr-o tranziție la starea LISTEN_B
    • Știm, de asemenea, că atunci când dispozitivul este în starea LISTEN_A sau LISTEN_B, dispozitivul ar trebui să reacționeze la butonul A eliberat și respectiv butonul B eliberat, trecând înapoi la starea NESIGNAT.
    • În cele din urmă, știm că atunci când dispozitivul este în starea TEAM_A sau TEAM_B, dispozitivul ar trebui să reacționeze la butonul A apăsat și butonul B apăsat prin difuzarea SIG_A și respectiv prin SIG_B.

      În acest moment nu este necesar să completați detaliile semnalelor de difuzare. Vom ajunge la asta mai târziu. Ceea ce este important este să instruim aceste funcții să folosească codul pe care îl vom scrie dând acel bloc de acțiuni un nume, cum ar fi broadcastSignalSIG_A, care descrie ce ar trebui făcut în acel moment

Pasul 5: Vrem să inițializăm valorile datelor în memoria dispozitivelor când dispozitivul pornește

Vrem să inițializăm valorile datelor în memoria dispozitivelor când dispozitivul pornește
Vrem să inițializăm valorile datelor în memoria dispozitivelor când dispozitivul pornește
Vrem să inițializăm valorile datelor în memoria dispozitivelor când dispozitivul pornește
Vrem să inițializăm valorile datelor în memoria dispozitivelor când dispozitivul pornește
Vrem să inițializăm valorile datelor în memoria dispozitivelor când dispozitivul pornește
Vrem să inițializăm valorile datelor în memoria dispozitivelor când dispozitivul pornește

În acest moment, am folosit o mulțime de variabile (nume pentru date), dar nu am atribuit de fapt valori acestor nume. Vrem ca dispozitivul să încarce valorile tuturor acestor variabile în memorie atunci când pornește, așa că plasăm inițializarea acestor variabile într-un bloc „la pornire”.

Acestea sunt valorile pe care trebuie să le inițializăm:

  • Constantele de semnal, conform protocolului de semnal. Valorile TREBUIE să fie:

    • SIG_R = 0
    • SIG_A = 1
    • SIG_B = 2
    • Notă: am prefixat aceste constante cu „EnumSignals” pentru a indica faptul că aceste variabile trebuie să se comporte ca și cum ar fi parte a unui tip enumerat numit Signals. Acesta este modul în care aceste variabile pot fi implementate în alte limbaje de programare. Definiția și explicația tipurilor enumerate depășește sfera tutorialului meu. O poate face Google dacă doresc acest lucru. Aceste prefixe sunt pur și simplu alegeri stilistice și nu sunt deloc esențiale pentru buna funcționare a programului.
  • Constantele de stare, care pot fi arbitrare atâta timp cât au o valoare. Am făcut o alegere de stil pentru a folosi pur și simplu numere întregi crescătoare de la 0, așa:

    • NESIGNAT = 0
    • LISTEN_A = 1
    • LISTEN_B = 2
    • TEAM_A = 3
    • TEAM_B = 4
    • Notă: Am luat aceeași decizie de stil în ceea ce privește prefixele și pentru aceste variabile. În plus, voi menționa că totul despre aceste atribuiri, valori și ordine, este complet arbitrar. Nici măcar nu contează că aceste valori sunt consistente de la dispozitiv la dispozitiv, deoarece sunt utilizate doar intern și nu pentru comunicarea prin rețea. Tot ceea ce contează este că variabilele au o valoare și că pot fi comparate între ele pentru a vedea dacă sunt echivalente sau nu.
  • Pentru lizibilitate, o constantă numită BOOT_STATE și setată la UNASSIGNED. Acest lucru face ca reseta la starea de încărcare, în loc de o stare mai arbitrară, să fie mai explicită atunci când dispozitivul primește un semnal de resetare, pe care îl vom implementa mai târziu.
  • Constante de animație, utilizate în pasul următor pentru a crea animații care permit întreruperi cu latență extrem de redusă prin intrarea utilizatorului. Nu le-am folosit până acum, dar cu siguranță vor fi explicate și utilizate în secțiunea următoare. Semnificația unora dintre acestea ar trebui să fie intuitivă datorită numelor lor.

    • 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
  • O altă variabilă pentru animație, de data aceasta un contor care cu siguranță nu este constant. La fel ca majoritatea ghișeelor, îl inițializăm la 0

    iTickLoadingAnimation = 0

  • Creați două serii de variabile pentru a conține cadre de animații. Prima, pe care o numesc „animație de încărcare”, ar trebui să aibă patru imagini (pe care probabil le-ați ghicit după ultima inițializare constantă), iar a doua, pe care o numesc „animație de difuzare”, care ar trebui să aibă trei imagini. Recomand denumirea variabilelor pentru a corespunde cadrelor animației, de ex. ringAnimation0, ringAnimation1 …

    Creați aceleași valori ale imaginii ca și mine sau creați imagini mai originale și mai cool

  • Nu în ultimul rând, trebuie să setăm grupul radio al dispozitivului la 0 folosind blocul „grupul de seturi radio (X)”
  • Opțional, scrieți mesajul „Inițializare finalizată” la ieșirea serială pentru a-i spune utilizatorului că totul a mers bine.
  • Acum că am terminat configurarea dispozitivului, putem apela funcția noastră de buclă de stare.

Pasul 6: Vrem să afișăm animații și grafică folosind afișajul LED 5 X 5

Vrem să afișăm animații și grafică folosind afișajul LED 5 X 5
Vrem să afișăm animații și grafică folosind afișajul LED 5 X 5
Vrem să afișăm animații și grafică folosind afișajul LED 5 X 5
Vrem să afișăm animații și grafică folosind afișajul LED 5 X 5
Vrem să afișăm animații și grafică folosind afișajul LED 5 X 5
Vrem să afișăm animații și grafică folosind afișajul LED 5 X 5

Și acum pentru ceva complet diferit.

Vrem să afișăm câteva animații și câteva caractere, dar nu vrem să întrerupem bucla de stare principală. Din păcate, blocurile care afișează imagini și șiruri de text au o întârziere de 400 ms în mod implicit. Nu există nicio modalitate de a schimba acest lucru fără a edita reprezentarea javascript a codului. Deci, asta vom face.

  • Creați o funcție pentru fiecare imagine. Acest lucru vă va permite să utilizați un singur bloc pentru a afișa imaginea în loc să editați javascript de fiecare dată. În acest program specific, nu se folosește nicio imagine de mai multe ori, dar cred că acest stil face codul mai ușor de citit.
  • Adăugați, în fiecare funcție nouă, un bloc „arată imaginea (X) la offset 0” cu numele variabilei de imagine corespunzătoare înlocuind (X)
  • Adăugați, în bucla de stare principală. „Afișați șiruri (X)” pentru fiecare bloc în afară de cel care gestionează starea NESIGNAT. Adăugați un caracter pentru ca dispozitivul să fie afișat pentru a indica stările sale diferite. Iată ce am făcut:

    • LISTEN_A: „a”
    • LISTEN_B: „b”
    • TEAM_A: „A”
    • TEAM_B: „B”

      Pentru statul NESIGNAT, efectuați un apel către o funcție care va actualiza animația de încărcare. Vom completa detaliile acestei funcții mai jos

  • Treceți la modul javascript.
  • Găsiți fiecare apel către X.showImage (0) și basic.showString (X)
  • Schimbați fiecare în X.showImage (0, 0) sau basic.showString (X, 0)

    • Adăugarea acestui argument suplimentar va seta întârzierea după acțiune la 0. În mod implicit, aceasta este lăsată în afara, iar dispozitivul se va întrerupe timp de 400 ms după executarea fiecăruia dintre aceste blocuri.
    • Acum, avem un mecanism aproape fără latență pentru a ne afișa imaginile în blocurile noastre de animație, pe care le putem construi acum

În primul rând, vom construi funcția de animație de difuzare relativ simplă. Este mai simplu, deoarece nu vrem ca utilizatorul să poată face nimic până când funcția nu este completă, astfel încât să îi oprim să trimită spam funcției de difuzare. Pentru a realiza acest lucru, putem pur și simplu păstra fluxul de control limitat la bloc până când funcția este completă, ceea ce reprezintă un comportament standard.

  • Creați o funcție care va afișa animația difuzată.
  • În interiorul acelui bloc, adăugați trei apeluri funcționale, câte unul la fiecare cadru al animației, în ordinea în care acestea trebuie afișate
  • Adăugați un bloc „wait (us) (X)” după fiecare apel la o funcție de afișare a imaginii.

    Notă: Acest bloc, din secțiunea de control avansat, va merge chiar mai departe de „pauză (ms)”, în sensul că va îngheța complet procesorul până la expirarea timpului specificat. Când se folosește blocul de pauză, este posibil ca dispozitivul să efectueze alte activități în culise. Acest lucru este imposibil cu blocul de așteptare

  • Înlocuiți (X) cu (MS_PER_FRAME_BROADCAST_ANIMATION x MICROSECONDS_PER_MILLISECOND)
  • Animația ar trebui să funcționeze corect

În al doilea rând, vom construi mecanismul pentru afișarea animației de încărcare. Ideea din spatele acestui lucru este de a actualiza afișajul LED la un anumit interval, pe care îl definim în variabila MS_PER_DEVICE_TICK. Această valoare, lungimea bifării dispozitivului, este numărul de milisecunde pe care dispozitivul îl întrerupe după finalizarea fiecărei iterații a buclei de stare. Deoarece această valoare este suficient de mică, putem actualiza afișajul o dată în timpul fiecărei iterații a buclei de afișare și îi va apărea utilizatorului că animația progresează perfect, iar când starea se schimbă, va exista o latență foarte mică între intrarea utilizatorului afișajul fiind actualizat. Prin numărarea căpușelor, pe care o facem cu variabila iTickLoadingAnimation, putem afișa cadrul corespunzător al animației.

  • Creați o funcție care va actualiza animația de încărcare
  • Adăugați o condiție pentru a verifica dacă contorul de căpușe și-a atins valoarea maximă. Această condiție va fi adevărată dacă valoarea contorului de bifă este mai mare decât numărul de cadre din animația de încărcare înmulțit cu numărul de bifări pentru a afișa fiecare cadru

    Dacă condiția este adevărată, resetați iTickLoadingAnimation la 0

  • Adăugați un bloc de condiții if-else. Acestea vor determina ce cadru al animației să se afișeze.

    Pentru fiecare cadru al animației, dacă contorul de căpușe este mai mic decât numărul de căpușe din fiecare animație înmulțit cu numărul de cadre al animației (începând de la 1), apoi afișați acel cadru, altfel verificați dacă următorul cadru este cel să fie afișat

  • În partea de jos a blocului, creșteți iTickLoadingAnimation
  • Animația ar trebui să funcționeze corect

Notă: Toate blocurile gri care apar în exemplul meu sunt generate atunci când se editează reprezentarea javascriptă a unui bloc. Înseamnă pur și simplu că blocul reprezintă cod javascript care nu poate fi reprezentat folosind setul standard de blocuri și trebuie editat sub formă de text.

Pasul 7: Vrem să transmitem date fără fir folosind radioul dispozitivului

Vrem să transmitem date fără fir folosind radioul dispozitivului
Vrem să transmitem date fără fir folosind radioul dispozitivului

Acest pas este mult mai scurt decât precedentul. De fapt, este probabil cel mai scurt pas din acest tutorial.

Amintiți-vă că, atunci când am programat răspunsul dispozitivului la intrarea utilizatorului, aveam două blocuri în captura de ecran care nu erau explicate în acea secțiune. Acestea erau apeluri către funcții care trimit semnale prin radio. Mai exact:

  • Butonul de pornire A apăsat:

    • Dacă dispozitivul este în stare TEAM_A:

      Semnal de difuzare SIG_A

  • Butonul de pornire B apăsat:

    • Dacă dispozitivul este în stare TEAM_B

      Semnal de difuzare SIG_B

Creați aceste funcții dacă nu există deja.

În fiecare funcție:

  • Apelați funcția de animație difuzată. Aceasta va împiedica orice altceva să se întâmple până la finalizare, care va fi în MS_PER_FRAME_BROADCAST_ANIMATION * 3 = 1,5 secunde. Constanta este înmulțită cu trei, deoarece există trei cadre în animație. Acest lucru este arbitrar și se pot adăuga mai multe dacă actualizarea estetică este suficient de mare. Un al doilea scop al acestei animații este de a împiedica un utilizator să trimită spam funcției de difuzare.
  • Adăugați un bloc „numărul de transmisie radio (X)”, unde este constanta semnalului menționată în numele funcției

Asta este tot ce trebuie să transmiteți prin radio.

Pasul 8: Vrem să ascultăm și să primim date prin intermediul dispozitivului și să le procesăm în consecință

Vrem să ascultăm și să primim date prin intermediul radioului dispozitivului și să le prelucrăm în consecință
Vrem să ascultăm și să primim date prin intermediul radioului dispozitivului și să le prelucrăm în consecință
Vrem să ascultăm și să primim date prin intermediul radioului dispozitivului și să le prelucrăm în consecință
Vrem să ascultăm și să primim date prin intermediul radioului dispozitivului și să le prelucrăm în consecință

Acesta este ultimul pas pentru crearea aplicației principale.

Vom spune dispozitivului cum să proceseze semnalele radio primite. În primul rând, dispozitivul nostru va denumi semnalul primit. Apoi, pe baza valorii semnalului, va decide ce acțiune să întreprindă, dacă este cazul.

Primul:

  1. Creați un bloc de cod începând cu un bloc „la radio recepționat (X)”.
  2. Opțional, atribuiți valoarea primită unei alte variabile cu un nume mai descriptiv.
  3. Apelați o funcție care va procesa semnalul

În al doilea rând, în funcția de procesare a semnalului:

  1. Creați un bloc de instrucțiuni if-else care controlează fluxul de ramificare pe baza valorii semnalului.
  2. Dacă semnalul a fost SIG_R

    Setați starea dispozitivului la BOOT_STATE (de aceea am creat această constantă mai devreme)

  3. Dacă semnalul a fost SIG_A și dacă starea curentă este LISTEN_A

    Setați starea dispozitivului la TEAM_A

  4. Dacă semnalul a fost SIG_B și dacă starea curentă este LISTEN_B

    Setați starea dispozitivului la TEAM_B

Asta e. Aplicația este terminată.

Pasul 9: Dispozitiv rădăcină: Vrem să fim capabili să selectăm un semnal

Dispozitiv rădăcină: Vrem să putem să selectăm un semnal
Dispozitiv rădăcină: Vrem să putem să selectăm un semnal

Acum, vom scrie o aplicație simplă pentru un dispozitiv „root”, adică un dispozitiv care va controla rețeaua.

Acest dispozitiv va trebui să îndeplinească două funcții:

  • Vrem să permitem utilizatorului să selecteze unul dintre semnalele noastre
  • Vrem să permitem utilizatorului să transmită semnalul

Deoarece specificația acestei aplicații este un subset al celei anterioare, voi oferi o prezentare generală, dar nu voi intra în atât de multe detalii pe cât am avut înainte. Imaginea de mai sus conține codul complet pentru această aplicație.

Pentru a permite utilizatorului să selecteze un semnal:

  1. Inițializați 5 variabile într-un bloc „la pornire”:

    1. Cele trei semnale (0, 1, 2)
    2. Numărul de semnale (3)
    3. O variabilă pentru a menține semnalul selectat în prezent (setat inițial la primul semnal, 0)
  2. Apăsați butonul A:

    1. Creșteți semnalul selectat
    2. Verificați dacă semnalul selectat este mai mare sau egal cu numărul de semnale

      Dacă da, setați semnalul selectat la 0

  3. După blocul de pornire, rulați o buclă „pentru totdeauna” care afișează valoarea curentă a semnalului selectat fără întârziere

Pentru a permite utilizatorului să transmită un semnal

  1. Setați grupul radio la 0 în blocul „la pornire”
  2. Apăsați butonul B:

    Transmiteți semnalul selectat folosind un bloc „numărul de transmisie radio (X)”

Asta e. Aplicația nodului rădăcină este extrem de simplă.

Pasul 10: Suntem Terminați

Suntem terminati
Suntem terminati

Mai sus este o imagine a dispozitivelor care rulează aplicația. Cei doi din dreapta rulează aplicația principală „utilizator”, iar cea din stânga rulează aplicația „rădăcină”.

Am demonstrat acest joc la CS Connections 2018, o conferință de vară de o săptămână pentru profesori de liceu și gimnaziu despre educația în informatică. Le-am dat profesorilor aproximativ 40 de dispozitive și le-am explicat regulile. Cei mai mulți au considerat că jocul este distractiv și mulți au găsit-o confuză până când au descoperit cum să joace. Demonstrația a fost scurtă, dar am găsit jocul plăcut în rândul unei mulțimi destul de diverse.

Mai multe informații despre CS Connections 2018 găsiți aici.

Recomandat: