Table des matières:

Network Rivalry : un jeu à faible latence pour la BBC Micro:bit : 10 étapes (avec photos)
Network Rivalry : un jeu à faible latence pour la BBC Micro:bit : 10 étapes (avec photos)

Vidéo: Network Rivalry : un jeu à faible latence pour la BBC Micro:bit : 10 étapes (avec photos)

Vidéo: Network Rivalry : un jeu à faible latence pour la BBC Micro:bit : 10 étapes (avec photos)
Vidéo: Benchmark HTTP grandeur nature (Julien Viet) 2024, Juillet
Anonim
Network Rivalry: un jeu à faible latence pour la BBC Micro:bit
Network Rivalry: un jeu à faible latence pour la BBC Micro:bit
Network Rivalry: un jeu à faible latence pour la BBC Micro:bit
Network Rivalry: un jeu à faible latence pour la BBC Micro:bit

Dans ce tutoriel, je vais vous expliquer comment implémenter un jeu multijoueur de base sur le BBC micro:bit avec les fonctionnalités suivantes:

  • Une interface simple
  • Faible latence entre les pressions sur les boutons et les mises à jour de l'écran
  • Un nombre flexible de participants
  • Contrôle facile du jeu à l'aide d'une télécommande principale ("root")

Le jeu est essentiellement une simulation de politique. Tous les joueurs commencent sans être affectés à aucune équipe, à l'exception de deux joueurs. L'un de ces joueurs est affecté à l'équipe A et l'autre à l'équipe B.

L'objectif du jeu pour chaque joueur d'être dans l'équipe avec la majorité des joueurs au moment où tout le monde est converti.

Le diagramme ci-dessus illustre une machine à états finis, c'est-à-dire une spécification des états dans lesquels l'appareil peut se trouver et les transitions entre ces états.

Un état peut être considéré comme l'ensemble de données actuel qui décrit la mémoire de l'appareil depuis sa mise sous tension. Sur la base de ces données, l'appareil peut effectuer certaines actions ou réagir différemment aux entrées de l'utilisateur.

Une transition est une condition logique qui, lorsqu'elle est vraie, entraîne le changement d'état du périphérique. Une transition peut être d'un état à n'importe quel autre état. Un état peut avoir plusieurs transitions.

Le schéma ci-dessus précise les états suivants:

  • Non attribué
  • Écoutez A
  • Écoutez B
  • Équipe A
  • Équipe B

Un appareil exécutant le code du jeu peut être dans l'un de ces cinq états, mais un seul à la fois, et seulement ces cinq-là.

Je supposerai tout au long du guide que vous utilisez l'éditeur MakeCode de Microsoft, qui peut être trouvé à l'adresse:

L'implémentation complète du jeu peut être trouvée ici:

makecode.microbit.org/_CvRMtheLbRR3 ("microbit-demo-user" est le nom du projet)

Et la mise en œuvre du contrôleur réseau maître ("root") peut être trouvée ici:

makecode.microbit.org/_1kKE6TRc9TgE ("microbit-demo-root" est le nom du projet)

Je ferai référence à ces exemples tout au long de mon tutoriel.

Étape 1: Considérations relatives à la conception d'une vue d'ensemble

Avant d'écrire du code, nous devons réfléchir à ce à quoi nous voulons que notre produit final ressemble. en d'autres termes, quelles sont les exigences de l'application ? Que doit indiquer notre code à l'appareil lorsqu'il a terminé ? J'ai divisé les fonctionnalités de l'application principale en six catégories, chacune pouvant être considérée sous un angle de conception différent.

  1. Nous voulons contrôler les actions de l'appareil en fonction de son état actuel
  2. Nous voulons que l'appareil réagisse aux entrées de l'utilisateur
  3. Nous pouvons souhaiter afficher des animations et des graphiques à l'aide de l'écran LED 5 x 5
  4. Nous voulons initialiser les valeurs de données dans la mémoire de l'appareil lorsque l'appareil démarre
  5. Nous voulons transmettre des données sans fil en utilisant la radio de l'appareil
  6. Nous voulons écouter et recevoir des données sur la radio de l'appareil et les traiter en conséquence

Permettez-moi d'entrer un peu plus dans les détails de chacun.

1. Nous voulons contrôler les actions de l'appareil en fonction de son état actuel

Comme la plupart des autres programmes, l'exécution des instructions spécifiées par le code se fait ligne par ligne. Nous voulons que notre appareil exécute certaines instructions en fonction de son état interne, comme illustré par le schéma en haut de ce tutoriel. Nous pourrions écrire une série de conditions après chaque bloc de code qui vérifie que le périphérique devrait faire, mais cette approche peut devenir très compliquée très rapidement, nous utiliserons donc à la place une boucle infinie qui vérifie simplement une variable, et basée sur cette variable, exécute un ensemble spécifique d'instructions ou ne fait rien du tout. Cette variable sera identifiée par le suffixe "_state" à la fois dans notre application utilisateur et dans notre application racine.

2. Nous voulons que l'appareil réagisse aux entrées de l'utilisateur

Malgré l'exécution normale du code se produisant de manière séquentielle, c'est-à-dire une ligne à la fois, nous avons besoin que notre appareil réagisse aux pressions sur les boutons pendant que la boucle d'état principale détermine ce que l'appareil doit faire à un moment donné. À cette fin, l'appareil a la capacité d'envoyer des signaux au logiciel de niveau inférieur qui interagit avec le matériel, déclenchant ce qu'on appelle un événement. Nous pouvons écrire du code qui indique à l'appareil de faire quelque chose lorsqu'il détecte un type d'événement spécifique.

3. Nous voulons afficher des animations et des graphiques à l'aide de l'écran LED 5 x 5

Le mécanisme pour ce faire semble simple, mais le bloc affiche une image ajoute un délai caché de 400 ms. Parce que nous voulons que notre appareil continue à exécuter sa boucle d'état avec le moins de latence possible, nous devrons éditer le code javascript pour minimiser le délai.

4. Nous voulons initialiser les valeurs de données dans la mémoire de l'appareil lorsque l'appareil démarre

Avant que notre appareil ne fasse quoi que ce soit, l'application doit charger ses données en mémoire. Cela inclut les variables constantes nommées pour la lisibilité du code, les variables qui contiennent des images, qui peuvent faire partie d'une animation, et les variables de compteur qui doivent commencer à 0 pour fonctionner correctement. Nous nous retrouverons avec une longue liste de noms de variables et de leurs valeurs nouvellement attribuées. En tant que choix de style personnel, je désignerai des valeurs constantes, c'est-à-dire des valeurs que je n'aurai jamais besoin de modifier, en utilisant ALL_CAPS. Je vais également préfixer les identifiants de variables principales avec un nom de catégorie qui fait référence à une sorte d'objet ou de type dont relève l'identifiant. C'est dans le but de rendre le code plus facile à suivre. Je n'utiliserai jamais un nom de variable comme "item" ou "x" en raison de l'ambiguïté qui survient lorsque l'on essaie de déchiffrer le code.

5. Nous voulons transmettre des données sans fil en utilisant la radio de l'appareil

C'est en fait une tâche assez simple lors de l'utilisation du langage de blocs MakeCode. Nous configurons simplement tous les appareils sur le même groupe radio au démarrage, puis lorsque nous voulons envoyer un signal, nous pouvons transmettre un seul numéro au bloc "Radio send number" qui nous est fourni. Il est important que l'émetteur et le récepteur travaillent sur le même groupe radio, car sinon, ils enverront ou recevront sur des fréquences différentes et la communication échouera.

6. Nous voulons écouter et recevoir des données sur la radio de l'appareil et les traiter en conséquence

En gardant à l'esprit les mêmes considérations que l'élément précédent, nous écouterons les transmissions entrantes de la même manière que nous écouterons les entrées de l'utilisateur: avec un gestionnaire d'événements. Nous écrirons un bloc de code qui examinera tous les signaux entrants et vérifiera si une action doit être entreprise sans perturber la boucle d'état principale.

De plus, nous devrions brièvement considérer la conception de l'application racine beaucoup plus simple, un programme qui permettra à un appareil de contrôler l'ensemble du réseau. Je ne passerai pas beaucoup de temps là-dessus car c'est beaucoup plus simple que la conception ci-dessus et une grande partie est simplement une répétition. J'ai divisé la fonctionnalité du deice racine en trois catégories.

  1. Nous voulons pouvoir sélectionner un signal
  2. Nous voulons pouvoir transmettre un signal

-

1. Nous voulons pouvoir sélectionner un signal

Cela peut être fait en ayant simplement un bouton parcourant les signaux possibles. Comme il n'y en a que trois, cette approche suffira. En même temps, nous pouvons avoir une boucle qui réaffiche constamment le signal sélectionné, permettant à l'utilisateur d'appuyer sur un bouton et de voir le signal sélectionné apparaître sur l'écran LED avec très peu de latence.

2. Nous voulons pouvoir transmettre un signal

Comme il y a deux boutons, on peut en désigner un pour la sélection et l'autre pour la confirmation. Comme l'application utilisateur, nous envoyons simplement le signal sur le réseau sous forme de numéro. Aucune autre information n'est requise.

Je parlerai davantage du protocole de signal simple dans la section suivante.

Étape 2: Le protocole de signal: un langage simple pour la communication réseau

Les signaux suivants peuvent être considérés comme l'ensemble de tous les mots possibles que les appareils peuvent utiliser pour se parler. Parce que le réseau est si simple, il n'y a pas grand-chose à dire, et nous pouvons donc représenter ces trois signaux par des valeurs entières simples.

0. Réinitialiser

  • Identifiant dans le code: SIG-R
  • Valeur entière: 0
  • Objectif: dire à tous les appareils à portée d'abandonner ce qu'ils sont en train de faire et d'agir comme s'ils venaient d'être démarrés. Si ce signal atteint tous les appareils du réseau, l'ensemble du réseau sera réinitialisé et les utilisateurs pourront commencer une nouvelle partie. Ce signal ne peut être diffusé que par un périphérique racine.

1. Conversion A

  • Identifiant dans le code: SIG-A
  • Valeur entière: 1
  • Objectif: dire à tout appareil qui est dans l'état LISTEN_A, une fois qu'il reçoit le signal de conversion, de passer à l'état TEAM_A.

2. Conversion B

  1. Identifiant dans le code: SIG-B
  2. Valeur entière: 2
  3. Objectif: dire à tout appareil qui est dans l'état LISTEN_B, une fois qu'il reçoit le signal de conversion, de passer à l'état TEAM_B.

Étape 3: Nous voulons contrôler les actions de l'appareil en fonction de son état actuel

Nous voulons contrôler les actions de l'appareil en fonction de son état actuel
Nous voulons contrôler les actions de l'appareil en fonction de son état actuel
Nous voulons contrôler les actions de l'appareil en fonction de son état actuel
Nous voulons contrôler les actions de l'appareil en fonction de son état actuel
Nous voulons contrôler les actions de l'appareil en fonction de son état actuel
Nous voulons contrôler les actions de l'appareil en fonction de son état actuel

Enfin, nous pouvons commencer à écrire du code.

Tout d'abord, ouvrez un nouveau projet dans Make Code

  • Créer une nouvelle fonction. J'ai appelé mine loop car c'est la boucle principale de l'application
  • Ajoutez un bloc de boucle qui se répétera indéfiniment. J'ai utilisé while(true) car un vrai littéral ne sera jamais faux, donc le flux de contrôle de l'application ne quittera jamais la boucle
  • Ajoutez suffisamment de blocs if-else pour vérifier si l'appareil est dans l'un de ses cinq états possibles
  • Créer une variable pour conserver l'état actuel de l'appareil
  • Créer des variables pour représenter chacun des cinq états possibles

    Remarque: Il est normal que ces variables n'aient pas encore de valeurs affectées. Nous y arriverons. À ce stade, il est plus important que nous écrivions un code propre et facile à lire

  • Modifiez chaque condition dans les blocs if-else pour comparer l'état actuel à l'un des états possibles
  • Au bas des blocs if-else, ajoutez une pause d'un certain nombre de millisecondes et créez une variable pour contenir ce nombre. Nous l'initialiserons plus tard. Assurez-vous que la variable a un nom descriptif, tel que tick ou heartbeat. Comme il s'agit de la boucle principale de l'appareil, cette pause déterminera la vitesse à laquelle l'appareil exécute la boucle principale. Il s'agit donc d'une valeur très importante et trop importante pour être un nombre magique sans nom.

Remarque: Ne vous inquiétez pas pour les blocs gris de la troisième image. J'y reviendrai plus tard.

Étape 4: Nous voulons réagir à l'entrée de l'utilisateur

Nous voulons réagir aux commentaires des utilisateurs
Nous voulons réagir aux commentaires des utilisateurs
Nous voulons réagir aux commentaires des utilisateurs
Nous voulons réagir aux commentaires des utilisateurs

Maintenant, nous voulons dire à l'appareil comment gérer les pressions sur les boutons. La première idée pourrait être d'utiliser simplement les blocs "Quand le bouton est enfoncé" dans la catégorie d'entrée, mais nous aimerions un contrôle plus granulaire que cela. Nous utiliserons le bloc "on event from (X) with value (Y)" from the control category sous la section advanced, car nous sommes avancés dans ce tutoriel.

  • Créez quatre blocs « sur événement de… ».

    • Deux d'entre eux devraient vérifier la source de l'événement "MICROBIT_ID_BUTTON_A"
    • Deux d'entre eux devraient vérifier la source de l'événement "MICROBIT_ID_BUTTON_B"
    • Parmi les deux événements ciblant chaque bouton:

      • Il faut vérifier l'événement de type "MICROBIT_BUTTON_EVT_UP"
      • Il faut vérifier l'événement de type "MICROBIT_BUTTON_EVT_DOWN"
    • Remarque: Ces options en lettres majuscules sont des étiquettes utilisées dans le code micro:bit de niveau inférieur. Ce sont simplement des espaces réservés qui sont ensuite remplacés par des entiers lorsque le code est compilé en un binaire exécutable. Il est plus facile pour les humains d'utiliser ces étiquettes que de rechercher quel entier mettre, bien que les deux fonctionnent de la même manière.
  • J'ai choisi, pour une question de style, que chaque bloc "on event from…" appelle une fonction qui décrit l'événement déclenché. Bien que cela ne soit pas strictement nécessaire, à mon avis, cela améliore la lisibilité. Si l'on souhaite le faire, ils peuvent mettre leur code de gestion des événements dans le bloc "on event from…" lui-même.

    Remarque: Le bloc de code qui gère la réponse de l'appareil à un événement est appelé intuitivement un « gestionnaire d'événements »

  • Ajoutez, dans chaque gestionnaire d'événements, la même structure if-else utilisée pour diviser le flux de contrôle en fonction de l'état du périphérique que la structure dans la boucle d'état principale.
  • Ajoutez des blocs d'affectation qui modifient cet état de l'appareil comme spécifié par notre diagramme d'état

    • On sait que lorsque l'appareil est dans l'état NON ASSIGNÉ, l'appareil doit réagir à l'appui sur le bouton A par une transition vers l'état LISTEN_A, et au bouton B enfoncé par une transition vers l'état LISTEN_B
    • Nous savons également que lorsque le dispositif est dans l'état LISTEN_A ou LISTEN_B, le dispositif doit réagir au relâchement du bouton A et du bouton B, respectivement, en revenant à l'état NON ASSIGNÉ.
    • Enfin, on sait que lorsque le dispositif est dans l'état TEAM_A ou TEAM_B, le dispositif doit réagir à l'appui sur le bouton A et sur le bouton B en diffusant respectivement SIG_A et SIG_B.

      Il n'est pas nécessaire à ce stade de renseigner les détails des signaux de diffusion. Nous y reviendrons plus tard. Ce qui est important, c'est que nous indiquions à ces fonctions d'utiliser le code que nous écrirons en donnant un nom à ce bloc d'actions, comme broadcastSignalSIG_A, qui décrit ce qui doit être fait à ce stade

Étape 5: Nous voulons initialiser les valeurs de données dans la mémoire des appareils au démarrage de l'appareil

Nous voulons initialiser les valeurs de données dans la mémoire des appareils lorsque l'appareil démarre
Nous voulons initialiser les valeurs de données dans la mémoire des appareils lorsque l'appareil démarre
Nous voulons initialiser les valeurs de données dans la mémoire des appareils lorsque l'appareil démarre
Nous voulons initialiser les valeurs de données dans la mémoire des appareils lorsque l'appareil démarre
Nous voulons initialiser les valeurs de données dans la mémoire des appareils lorsque l'appareil démarre
Nous voulons initialiser les valeurs de données dans la mémoire des appareils lorsque l'appareil démarre

À ce stade, nous avons utilisé beaucoup de variables (noms pour les données), mais nous n'avons pas réellement affecté de valeurs à ces noms. Nous voulons que l'appareil charge les valeurs de toutes ces variables en mémoire lorsqu'il démarre, nous plaçons donc l'initialisation de ces variables dans un bloc "au démarrage".

Voici les valeurs que nous devons initialiser:

  • Constantes de signal, selon le protocole de signal. Les valeurs DOIVENT être:

    • SIG_R = 0
    • SIG_A = 1
    • SIG_B = 2
    • Remarque: J'ai préfixé ces constantes par « EnumSignals » afin d'indiquer que ces variables doivent se comporter comme si elles faisaient partie d'un type énuméré appelé Signaux. C'est ainsi que ces variables peuvent être implémentées dans d'autres langages de programmation. La définition et l'explication des types énumérés dépassent le cadre de mon tutoriel. On peut le rechercher sur Google s'ils le souhaitent. Ces préfixes sont simplement des choix stylistiques et ne sont pas du tout indispensables au bon fonctionnement du programme.
  • Constantes d'état, qui peuvent être arbitraires tant qu'elles ont une valeur. J'ai fait un choix de style pour utiliser simplement des entiers croissants à partir de 0, comme ceci:

    • NON AFFECTÉ = 0
    • ÉCOUTEZ_A = 1
    • ÉCOUTEZ_B = 2
    • ÉQUIPE_A = 3
    • ÉQUIPE_B = 4
    • Remarque: j'ai également pris la même décision de style concernant les préfixes pour ces variables. De plus, je mentionnerai que tout ce qui concerne ces affectations, les valeurs et l'ordre, est complètement arbitraire. Peu importe que ces valeurs soient cohérentes d'un appareil à l'autre, car elles ne sont utilisées qu'en interne et non pour la communication sur le réseau. Tout ce qui compte, c'est que les variables aient une valeur et qu'elles puissent être comparées entre elles pour voir si elles sont équivalentes ou non.
  • Pour plus de lisibilité, une constante appelée BOOT_STATE et définissez-la sur UNASSIGNED. Cela rend le fait que nous réinitialisons à l'état de démarrage, au lieu d'un état plus arbitraire, plus explicite lorsque l'appareil reçoit un signal de réinitialisation, que nous implémenterons plus tard.
  • Constantes d'animation, utilisées à l'étape suivante pour créer des animations permettant une interruption à très faible latence via l'entrée de l'utilisateur. Nous ne les avons pas utilisés jusqu'à présent, mais ils seront certainement expliqués et utilisés dans la section suivante. La signification de certains d'entre eux devrait être intuitive en raison de leurs noms.

    • 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
  • Autre variable pour l'animation, cette fois un compteur qui n'est décidément pas constant. Comme la plupart des compteurs, nous l'initialisons à 0

    iTickChargementAnimation = 0

  • Créez deux séries de variables pour contenir des images d'animations. La première, que j'appelle "l'animation de chargement", devrait avoir quatre images (que vous avez peut-être deviné par la dernière initialisation constante), et la seconde, que j'appelle "l'animation de diffusion", qui devrait avoir trois images. Je recommande de nommer les variables pour qu'elles correspondent aux images de l'animation, par ex. ringAnimation0, ringAnimation1…

    Créez les mêmes valeurs d'image que moi ou créez des images plus originales et plus cool

  • Last but not least, nous devons mettre le groupe radio de l'appareil à 0 à l'aide du bloc "radio set group (X)"
  • Éventuellement, écrivez le message "Initialisation terminée" sur la sortie série pour indiquer à l'utilisateur que tout s'est bien passé.
  • Maintenant que nous avons fini de configurer l'appareil, nous pouvons appeler notre fonction de boucle d'état.

Étape 6: Nous voulons afficher des animations et des graphiques à l'aide de l'écran LED 5 X 5

Nous voulons afficher des animations et des graphiques à l'aide de l'écran LED 5 X 5
Nous voulons afficher des animations et des graphiques à l'aide de l'écran LED 5 X 5
Nous voulons afficher des animations et des graphiques à l'aide de l'écran LED 5 X 5
Nous voulons afficher des animations et des graphiques à l'aide de l'écran LED 5 X 5
Nous voulons afficher des animations et des graphiques à l'aide de l'écran LED 5 X 5
Nous voulons afficher des animations et des graphiques à l'aide de l'écran LED 5 X 5

Et maintenant pour quelque chose de complètement différent.

Nous voulons afficher quelques animations et quelques personnages, mais nous ne voulons pas interrompre la boucle d'état principale. Malheureusement, les blocs qui affichent des images et des chaînes de texte ont un délai de 400 ms par défaut. Il n'y a aucun moyen de changer cela sans modifier la représentation javascript du code. Alors, c'est ce que nous allons faire.

  • Créez une fonction pour chaque image. Cela permettra d'utiliser un seul bloc pour afficher l'image au lieu d'éditer le javascript à chaque fois. Dans ce programme spécifique, aucune image n'est utilisée plus d'une fois, mais je pense toujours que ce style rend le code plus facile à lire.
  • Ajouter, dans chaque nouvelle fonction, un bloc "afficher l'image (X) à l'offset 0" avec le nom de la variable image correspondante remplaçant (X)
  • Ajouter, dans la boucle d'état principale. "Afficher la chaîne (X)" blocs à chaque bloc en plus de celui qui gère l'état NON ASSIGNÉ. Ajoutez un caractère à afficher par l'appareil pour indiquer ses différents états. Voici ce que j'ai fait:

    • LISTEN_A: "a"
    • LISTEN_B: "b"
    • ÉQUIPE_A: « A »
    • ÉQUIPE_B: « B »

      Pour l'état NON ASSIGNÉ, appelez une fonction qui mettra à jour l'animation de chargement. Nous allons remplir les détails de cette fonction ci-dessous

  • Passez en mode javascript.
  • Trouvez chaque appel à X.showImage(0) et basic.showString(X)
  • Remplacez chacun par X.showImage(0, 0) ou basic.showString(X, 0)

    • L'ajout de cet argument supplémentaire définira le délai après l'action à 0. Par défaut, cela est omis et l'appareil fera une pause de 400 ms après l'exécution de chacun de ces blocs.
    • Maintenant, nous avons un mécanisme presque sans latence pour afficher nos images dans nos blocs d'animation, que nous pouvons maintenant construire

Tout d'abord, nous allons construire la fonction d'animation de diffusion relativement simple. C'est plus simple car nous ne voulons pas que l'utilisateur puisse faire quoi que ce soit tant que la fonction n'est pas terminée, afin de l'empêcher de spammer la fonction de diffusion. Pour ce faire, nous pouvons simplement garder le flux de contrôle limité au bloc jusqu'à ce que la fonction soit terminée, ce qui est un comportement standard.

  • Créez une fonction qui affichera une animation de diffusion.
  • À l'intérieur de ce bloc, ajoutez trois appels de fonction, un pour chaque image de l'animation, dans l'ordre dans lequel ils doivent être affichés
  • Ajoutez un bloc "wait (us) (X)" après chaque appel à une fonction d'affichage d'images.

    Remarque: Ce bloc, de la section de contrôle avancé, ira encore plus loin que « pause (ms) » en ce sens qu'il gèlera complètement le processeur jusqu'à ce que le temps spécifié se soit écoulé. Lorsque le bloc de pause est utilisé, il est possible que l'appareil effectue d'autres tâches en coulisses. C'est impossible avec le bloc d'attente

  • Remplacer (X) par (MS_PER_FRAME_BROADCAST_ANIMATION x MICROSECONDS_PER_MILLISECOND)
  • L'animation devrait maintenant fonctionner correctement

Deuxièmement, nous allons construire le mécanisme d'affichage de l'animation de chargement. L'idée derrière cela est de mettre à jour l'affichage LED à un intervalle spécifique, que nous définissons dans la variable MS_PER_DEVICE_TICK. Cette valeur, la longueur de graduation du périphérique, correspond au nombre de millisecondes pendant lesquelles le périphérique s'interrompt après chaque itération de la boucle d'état. Parce que cette valeur est suffisamment petite, nous pouvons mettre à jour l'affichage une fois au cours de chaque itération de la boucle d'affichage et il apparaîtra à l'utilisateur que l'animation progresse de manière transparente, et lorsque l'état change, il y aura très peu de latence entre l'entrée de l'utilisateur l'affichage étant mis à jour. En comptant les ticks, ce que nous faisons avec la variable iTickLoadingAnimation, nous pouvons afficher le cadre approprié de l'animation.

  • Créer une fonction qui mettra à jour l'animation de chargement
  • Ajoutez une condition pour vérifier si le compteur de ticks a atteint sa valeur maximale. Cette condition sera vraie si la valeur du compteur de ticks est supérieure au nombre d'images dans l'animation de chargement multiplié par le nombre de ticks pour afficher chaque image

    Si la condition est vraie, réinitialisez iTickLoadingAnimation à 0

  • Ajoutez un bloc de conditions if-else. Ceux-ci détermineront quelle image de l'animation afficher.

    Pour chaque image de l'animation, si le compteur de ticks est inférieur au nombre de ticks dans chaque animation multiplié par le numéro d'image de l'animation (commençant à 1), alors affichez cette image, sinon vérifiez si l'image suivante est celle à être affichées

  • En bas du bloc, incrémentez iTickLoadingAnimation
  • L'animation devrait maintenant fonctionner correctement

Remarque: Tous les blocs gris qui apparaissent dans mon exemple sont générés lorsque l'on modifie la représentation javascript d'un bloc. Cela signifie simplement que le bloc représente du code javascript qui ne peut pas être représenté à l'aide de l'ensemble standard de blocs et doit être édité sous forme de texte.

Étape 7: Nous voulons transmettre des données sans fil à l'aide de la radio de l'appareil

Nous voulons transmettre des données sans fil à l'aide de la radio de l'appareil
Nous voulons transmettre des données sans fil à l'aide de la radio de l'appareil

Cette étape est beaucoup plus courte que la précédente. En fait, c'est probablement l'étape la plus courte de tout ce tutoriel.

Rappelez-vous que lorsque nous avons programmé la réponse de l'appareil à la saisie de l'utilisateur, j'avais deux blocs dans la capture d'écran qui n'étaient pas expliqués dans cette section. Il s'agissait d'appels à des fonctions qui envoient des signaux par radio. Plus précisement:

  • Sur le bouton A enfoncé:

    • Si l'appareil est dans l'état TEAM_A:

      Signal de diffusion SIG_A

  • Sur le bouton B enfoncé:

    • Si l'appareil est dans l'état TEAM_B

      Signal de diffusion SIG_B

Créez ces fonctions si elles n'existent pas déjà.

Dans chaque fonction:

  • Appelez la fonction d'animation de diffusion. Cela empêchera tout le reste de se produire jusqu'à ce qu'il se termine, ce qui sera dans MS_PER_FRAME_BROADCAST_ANIMATION * 3 = 1,5 secondes. La constante est multipliée par trois car il y a trois images dans l'animation. Ceci est arbitraire et d'autres peuvent être ajoutés si la mise à niveau esthétique est suffisamment importante. Un deuxième objectif de cette animation est d'empêcher un utilisateur de spammer la fonction de diffusion.
  • Ajoutez un bloc "numéro d'envoi radio (X)", où est la constante de signal mentionnée dans le nom de la fonction

C'est tout ce qu'il faut pour diffuser à la radio.

Étape 8: Nous voulons écouter et recevoir des données sur la radio de l'appareil et les traiter en conséquence

Nous voulons écouter et recevoir des données sur la radio de l'appareil et les traiter en conséquence
Nous voulons écouter et recevoir des données sur la radio de l'appareil et les traiter en conséquence
Nous voulons écouter et recevoir des données sur la radio de l'appareil et les traiter en conséquence
Nous voulons écouter et recevoir des données sur la radio de l'appareil et les traiter en conséquence

Il s'agit de la dernière étape pour créer l'application principale.

Nous allons indiquer à l'appareil comment traiter les signaux radio entrants. Tout d'abord, notre appareil va nommer le signal reçu. Ensuite, en fonction de la valeur de ce signal, il décidera de l'action à entreprendre, le cas échéant.

D'abord:

  1. Créez un bloc de code commençant par un bloc "sur radio reçue (X)".
  2. Vous pouvez éventuellement affecter cette valeur reçue à une autre variable avec un nom plus descriptif.
  3. Appeler une fonction qui traitera le signal

Deuxièmement, dans la fonction de traitement du signal:

  1. Créez un bloc d'instructions if-else qui bifurquent le flux de contrôle en fonction de la valeur du signal.
  2. Si le signal était SIG_R

    Définissez l'état de l'appareil sur BOOT_STATE (c'est pourquoi nous avons créé cette constante plus tôt)

  3. Si le signal était SIG_A et si l'état actuel est LISTEN_A

    Définir l'état de l'appareil sur TEAM_A

  4. Si le signal était SIG_B et si l'état actuel est LISTEN_B

    Définir l'état de l'appareil sur TEAM_B

C'est ça. L'application est terminée.

Étape 9: Périphérique racine: nous voulons pouvoir sélectionner un signal

Périphérique racine: nous voulons pouvoir sélectionner un signal
Périphérique racine: nous voulons pouvoir sélectionner un signal

Maintenant, nous allons écrire une application simple pour un appareil "root", c'est-à-dire un appareil qui va contrôler le réseau.

Cet appareil devra remplir deux fonctions:

  • Nous voulons permettre à l'utilisateur de sélectionner l'un de nos signaux
  • Nous voulons permettre à l'utilisateur de diffuser le signal

Parce que la spécification de cette application est un sous-ensemble de la précédente, je vais donner un aperçu mais je n'entrerai pas dans autant de détails qu'avant. L'image ci-dessus contient le code complet de cette application.

Pour permettre à l'utilisateur de sélectionner un signal:

  1. Initialiser 5 variables dans un bloc "au démarrage":

    1. Les trois signaux (0, 1, 2)
    2. Le nombre de signaux (3)
    3. Une variable pour contenir le signal actuellement sélectionné (initialement défini sur le premier signal, 0)
  2. Gérez une pression sur le bouton A:

    1. Incrémenter le signal sélectionné
    2. Vérifiez si le signal sélectionné est supérieur ou égal au nombre de signaux

      Si c'est le cas, réglez le signal sélectionné sur 0

  3. Après le bloc de démarrage, exécutez une boucle « pour toujours » qui affiche la valeur de signal actuellement sélectionnée sans délai

Pour permettre à l'utilisateur de diffuser un signal

  1. Mettre le groupe radio à 0 dans le bloc "on start"
  2. Gérez une pression sur le bouton B:

    Diffusez le signal sélectionné à l'aide d'un bloc "numéro d'envoi radio (X)"

C'est ça. L'application du nœud racine est extrêmement simple.

Étape 10: Nous avons terminé

Nous avons finis
Nous avons finis

Ci-dessus, une image des appareils exécutant l'application. Les deux à droite exécutent l'application "utilisateur" principale et celui de gauche exécute l'application "root".

J'ai présenté ce jeu à CS Connections 2018, une conférence d'été d'une semaine pour les enseignants des collèges et lycées sur l'enseignement de l'informatique. J'ai distribué une quarantaine d'appareils aux enseignants et expliqué les règles. La plupart ont trouvé le jeu divertissant, et beaucoup l'ont trouvé déroutant jusqu'à ce qu'ils sachent comment jouer. La démonstration a été courte, mais nous avons trouvé le jeu agréable auprès d'un public assez diversifié.

Vous trouverez plus d'informations sur CS Connections 2018 ici.

Conseillé: